<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
  <title>IaC カテゴリ | フューチャー技術ブログ</title>
  <subtitle>IaC カテゴリの記事一覧</subtitle>
  <icon>https://future-architect.github.io/feed_icon.png</icon>
  <logo>https://future-architect.github.io/apple-touch-icon.png</logo>
  <webfeeds:icon>https://future-architect.github.io/apple-touch-icon.png</webfeeds:icon>
  <webfeeds:accentColor>258fb8</webfeeds:accentColor>
  <link href="https://future-architect.github.io/categories/IaC/atom.xml" rel="self"/>
  <link href="https://future-architect.github.io/categories/IaC/"/>
  <updated>2026-05-31T15:00:00.000Z</updated>
  <id>https://future-architect.github.io/categories/IaC/</id>
  <generator uri="https://hexo.io/">Hexo</generator>
  <entry>
    <title>Terraformに関してAIに聞いてみて初心者目線で疑問点を公式ドキュメントで解消してみた</title>
    <link href="https://future-architect.github.io/articles/20260601a/"/>
    <id>https://future-architect.github.io/articles/20260601a/</id>
    <published>2026-05-31T15:00:00.000Z</published>
    <updated>2026-05-31T15:00:00.000Z</updated>
    <author><name>永井辰弥</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260601a/top.jpg" alt="" width="512" height="279">

<p>本記事はTerraform 連載 2026 最後の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>製造エネルギーグループの永井です。</p>
<p>AIって便利ですよね。新しい分野の学習にAIは欠かせなくなりました。ただAIはもっともらしい嘘をつく可能性もあり、信頼できる一次情報（公式ドキュメント）に当たることは重要だと思います。私もこれまでAIの回答に対して「いやそんなわけなかろう」と何度も突っ込んできました。</p>
<p>本記事ではAIと公式ドキュメントを行ったり来たりしながらTerraformについて基本的な知識を整理していきます。</p>
<h2 id="Terraformの基本とメリット">Terraformの基本とメリット</h2><div class="note-container note-tip note-has-title"><div class="note-title"><span class="note-icon"></span>Terraformとは何か？（AIの回答）</div><div class="note-body">

<p>Terraformとは、インフラストラクチャをコードとして管理（IaC: Infrastructure as Code）し、自動構築や安全なバージョン管理を可能にするオープンソースのツールです。</p>
<p>なぜTerraformが必要なのかというと、手作業によるサーバー構築の手間や設定ミス（ヒューマンエラー）を徹底的に排除し、「いつでも、誰が実行しても、全く同じ環境を再現できる（冪等性）」という状態を作り出せるからです。</p>
<ul>
<li><strong>従来の構築方法</strong>: クラウドの管理画面（GUI）を操作し、手順書を見ながら手作業で環境を作成。</li>
<li><strong>Terraformを使った構築</strong>: 各クラウドと接続する「Provider」や、現在の状態を記録する「tfstateファイル」という仕組みを介し、コードに書いた理想の状態を自動再現。</li>
</ul>
<p>例えば、AWSやGoogle Cloud上でサーバーを立ち上げる際も、Terraformがコードと実際の環境（tfstate）の差分を読み取り、Provider経由で自動構築してくれます。</p>
<p>したがって、インフラ構築の「効率化」「自動化」「属人化の解消」を図る上で、Terraformは現代のシステム開発において欠かせない技術となっています。</p>
</div></div>

<p>メリットはよくわかりました。</p>
<p>実際のコンソール画面でリソースを構築してみると設定項目がたくさんあります。また利用料金節約のために、環境ごとに設定を変えるのもよくある運用だと思います。ヒューマンエラーを排除してくれるのはありがたいです。</p>
<p>ただちょっとProviderやtfstateなどよくわからない言葉が出てきたので、それぞれの言葉について公式ドキュメントをあたってみましょう。</p>
<h2 id="ProviderとTerraform-Coreの役割分担">ProviderとTerraform Coreの役割分担</h2><p>Providerに関する公式ドキュメントのURLを教えてもらって自分で読んでみます。</p>
<p>公式ドキュメント（Providers）を確認しました。</p>
<p>初心者が知っておくとよさそうな部分をまとめます。</p>
<ul>
<li>Terraformが各Cloudとやり取りするためのルールはProviderが規定している。</li>
<li>Terraform Registryという場所にProviderは規定されている。</li>
<li>Terraform Registryに変数などの使い方など基本的なドキュメントはそろっている。</li>
<li>個人でTerraform Providerを作ることもできる。（Docker imageみたいですね。公式のProviderから選択するのが無難そうです。）</li>
</ul>
<p>ん？？ ではTerraform自体はなにしてるんでしょう？</p>
<div class="note-container note-tip note-has-title"><div class="note-title"><span class="note-icon"></span>Terraform本体は何をしているのか？（AIの回答）</div><div class="note-body">

<p>結論から言うと、Terraform本体（Terraform Core）は、理想の設計図（コード）と現在の状況（tfstate）を見比べて「次に何をすべきかの計画（差分）」を計算し、Providerに的確な指示を出す「現場監督」の役割を担っています。</p>
<p>なぜなら、Providerが「AWS専用の作業員」や「GCP専用の作業員」だとすれば、誰かが「システム全体の設計図を読み解き、現在の進捗を確認し、どの作業員にどういう順番で動いてもらうか」を取り仕切る必要があるからです。</p>
<p>具体的にTerraform本体（Core）は下図のフローで動いています。</p>
<p>つまり、Terraform本体はクラウドに直接触れることはなく、「状態管理と差分計算をする非常に賢いエンジン」として機能しています。この明確な役割分担があるからこそ、TerraformはAWSでもGCPでもローカルPCでも、同じ「Plan（計画）してApply（適用）する」という統一された体験を提供できるのです。</p>
</div></div>

<pre class="mermaid" data-mermaid="f2b9c1139fd1ebf183ba93eec8b2dcff6c18a88035983245262b6ee672eb3e1f">flowchart TD
    classDef extNode fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1.5px,color:#37474f,rx:6px,ry:6px
    classDef coreStep fill:#BBCCF6,stroke:#546e7a,stroke-width:2px,color:#263238,rx:6px,ry:6px

    UserHCL[人間が書いたHCL<br>（理想の設計図）]:::extNode
    StateFile[(terraform.tfstate<br>現実のインフラ状態)]:::extNode

    subgraph TerraformCore [Terraform Core の4つの役割]
        direction TB
        style TerraformCore fill:#ffffff,stroke:#78909c,stroke-width:1.5px,stroke-dasharray: 5 5,color:#37474f

        Step1(1. コードの解析<br>文法や依存関係を解釈):::coreStep
        Step2(2. 状態の管理<br>ファイルの読み書き):::coreStep
        Step3(3. 実行計画の作成 / Plan<br>理想と現実の差分を計算):::coreStep
        Step4(4. Providerへの指示出し / Apply<br>正しい順番で命令):::coreStep

        Step1 --> Step3
        Step2 --> Step3
        Step3 --> Step4
    end

    ProviderNode[各Provider<br>（AWS, GCPなどのAPIを叩く）]:::extNode

    UserHCL --> Step1
    StateFile <--> Step2
    Step4 --> ProviderNode</pre>

<p>作業をキューイングしたり、人が書いたHCLを翻訳したりと、アプリケーションでいうところのOSに近い部分を担っていることがわかりましたが、Terraform Coreという新しい言葉が出てきました。</p>
<h3 id="Terraform-Coreについて公式ドキュメントによると">Terraform Coreについて公式ドキュメントによると</h3><p>これも公式ドキュメントを読んでみました。</p>
<p>要点をまとめます。</p>
<ul>
<li>Terraform Core と Terraform Plugins に分かれる</li>
<li>Terraform CoreはRPCというプロトコルを用いてTerraform Pluginsとやり取りする</li>
<li><strong>Terraform Core（本体）の役割</strong><ul>
<li>設定ファイルやモジュールの読み込み、変数の展開・計算</li>
<li>構築・削除・更新など、リソースの状態管理（State）</li>
<li>リソースの依存関係のグラフ化</li>
<li>実行計画の作成（実際のインフラへ適用する前に、どのような変更が起きるかを事前計算・プレビューする）</li>
</ul>
</li>
<li><strong>Terraform Plugins（Providerなど）の役割</strong><ul>
<li>インフラストラクチャプロバイダー（AWSやGCPなど）との認証</li>
<li>私たちが書いた <code>resource</code> や <code>data</code> というコードブロックを、実際のクラウドサービスの機能（API）へ正確に繋ぎ合わせる</li>
<li>各クラウド固有の便利機能の提供</li>
</ul>
</li>
</ul>
<p>Coreという名前だけあってOSのような役割を担っていることがよりイメージできるようになりました。特にユーザにとって重要なのは実行計画の作成ですね。</p>
<p>Terraform Planしたときに確認できる内容ですが、これもTerraform Coreが担っているとのことでした。Applyすると何がどう変化するのか事前に知っておかないと不安になるので、しっかり確認する必要があります。依存関係のグラフ化も気になりますが、そこまで深堀りするとちょっと長くなりそうなので今回は見送ります。</p>
<h2 id="Terraformを支える状態管理と実行計画">Terraformを支える状態管理と実行計画</h2><p>さて新しい疑問が出てきたのでさらに深堀りしていきます。<br>Terraform Coreの重要な役割である「状態管理」と「計画の実行」についてもAIに聞き、公式ドキュメントで裏付けを取りました。</p>
<div class="note-container note-tip note-has-title"><div class="note-title"><span class="note-icon"></span>状態管理（Resource state management）とは？（AIの回答）</div><div class="note-body">

<p>「現実のインフラが今どうなっているか」をTerraform本体が記憶しておく仕組み。</p>
<p>結論から言うと、状態管理（Resource state management）は、Terraform本体（Core）が <code>terraform.tfstate</code> というファイルを使って、自分が過去に作成したリソースの現在の状態を追跡・記憶しておく重要な役割です。</p>
<p>なぜこれが必要かというと、コード（HCL）には「最終的にどうなりたいか（理想）」しか書かれていないため、現実のクラウド環境と見比べないと「何を新しく作り、何を削除すべきか」が判断できないからです。</p>
<ul>
<li><strong>具体例</strong>: コードに「Webサーバーが3台必要」と書かれているとします。</li>
<li><strong>Stateがない場合</strong>: Terraformは今の状態を知らないため、毎回「よし、3台作ろう」と動いてしまい、実行するたびにサーバーが増え続けてしまいます。</li>
<li><strong>Stateがある場合</strong>: Stateファイルを見て「すでに2台存在しているな」と把握し、「あと1台だけ追加すればいい」という正確な判断が下せます。</li>
</ul>
<p>つまり、Terraform本体がこの「状態管理」を担っているおかげで、何度コマンドを実行しても同じ環境が保たれる「冪等性（べきとうせい）」が実現できているのです。</p>
</div></div>

<div class="note-container note-tip note-has-title"><div class="note-title"><span class="note-icon"></span>計画の実行（Plan execution）とは？（AIの回答）</div><div class="note-body">

<p>理想と現実の「差分」を計算し、Providerに正しい順番で指示を出す仕組み。</p>
<p>計画の実行（Plan execution）とは、Terraform本体（Core）がコード（理想）とState（現実）の差分から「どのリソースを、どういう順番で作成・変更・削除するか」という手順書を作成し、それを実行する役割のことです。</p>
<p>いきなりクラウドを操作するのではなく、まずは安全に「計画」を立ててから動くのがTerraformの大きな特徴です。</p>
<ul>
<li><strong>計画（Plan）</strong>: リソース同士の依存関係を計算します。「Webサーバーを作るには、先にネットワークが必要だ」といった順番をTerraform本体が自動で判断し、「この順序で進める」という実行計画（Execution Plan）を作成します。</li>
<li><strong>実行（Execution）</strong>: 立てた計画に沿って、Terraform本体が各Provider（AWSやGCPなど）に対して、「はい、次はネットワークを作って！」「それが終わったらサーバーを立てて！」とRPC通信で的確に指示を出します。</li>
</ul>
</div></div>

<h3 id="State周りについて公式ドキュメントによると">State周りについて公式ドキュメントによると</h3><p>例も交えてきれいにまとめまっていました。状態管理はTerraformの核となる機能のようです。理解しておくとTerraformへの理解も深まりそうなので公式ドキュメントを読んでみます。</p>
<ul>
<li>Stateの目的と概念</li>
<li>Stateファイルの仕様や仕組み全体</li>
<li>実行計画に関する公式ドキュメント</li>
<li>リソースの依存関係</li>
</ul>
<p>これらの公式ドキュメントを読むことで、以下の点がわかりました。</p>
<ul>
<li>Stateファイルによって「コード上の名前」と「クラウド上のID」を紐づけているからこそ、未作成や更新の判断が正確にできる。</li>
<li>Terraformはリソースの依存関係（参照・被参照）などのメタデータを管理している。</li>
<li>キャッシュを保持することで効率的にリソースを管理できる。</li>
<li>Stateファイルはリスク管理の観点からバックアップを取得してHCP Terraformに保存しておくのがおすすめ。</li>
<li>Stateファイルはユーザが自分で手編集できない。</li>
<li>暗黙の依存関係とユーザが規定する明示的な依存関係という仕組みを用いて、「どちらを先に作るべきか」を決定している。</li>
</ul>
<p>ここまでで、基本的な知識はついたのではないでしょうか。</p>
<h2 id="おわりに">おわりに</h2><p>AI便利ですね！ただ公式ドキュメントを確認する姿勢も大事です。</p>
<p>「とりあえず動くものを作るだけ」「ざっくり知識を得たい」くらいならAIに聞くだけで十分な範囲をカバーできそうです。ただ、ずっとAIと会話していると「合ってるんだろうか。」と不安になります。今回のように公式ドキュメントと1対1の割合にするくらいが精神衛生上よいなと感じました。また、当たり前ですが「公式ドキュメントのURLを教えて」というような具体的な指示の方がうまく動いてくれました。</p>
<p>さらに人が書くブログ記事にもまだまだ価値があると感じました。というのも、この記事は私が理解を得るにいたった経緯をまとめたものです。ということは他にも同じ経緯をたどって理解する人がいるはずです。日々の業務で忙しいですが自分の発信の機会も増やしていきたいです。</p>
<p>本記事ではAIと公式ドキュメントを行ったり来たりしながらTerraformについて基本的な知識を整理しました。</p>
<p>後は実際に動かしてみてさらに理解を深めようと思います。ここまでお付き合いいただきありがとうございました。</p>
]]></content>
    <summary type="html">AIって便利ですよね。新しい分野の学習にAIは欠かせなくなりました。ただAIはもっともらしい嘘をつく可能性もあり、信頼できる一次情報（公式ドキュメント）に当たることは重要だと思います。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="TerraformCloud" scheme="https://future-architect.github.io/tags/TerraformCloud/"/>
    <category term="ドキュメント" scheme="https://future-architect.github.io/tags/%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88/"/>
  </entry>
  <entry>
    <title>moto に Terraform を打ち込み、基本的なイベント駆動の構成を動かしてみた</title>
    <link href="https://future-architect.github.io/articles/20260528a/"/>
    <id>https://future-architect.github.io/articles/20260528a/</id>
    <published>2026-05-27T15:00:00.000Z</published>
    <updated>2026-05-27T15:00:00.000Z</updated>
    <author><name>市川裕也</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>こんにちは。 CSIG の市川です。</p>
<p>Terraform連載ということで、Terraform を打ち込むことができる AWS エミュレータの「moto」を紹介します。</p>
<p>ローカルや CI 上で AWS・ Terraform のテストをしたいときのエミュレータとして、これまでは LocalStack が採用されるケースが多かったかと思います。</p>
<p>ただ 2026 年 3 月に Community Edition が廃止され、統合イメージへの移行と認証トークンの必須化が行われました。その後フィードバックを受けて非商用限定の Hobby プランも用意されましたが、いずれにせよ「アカウント登録 + 認証トークン」が必要になっています。 (参考: https://www.localstack.cloud/pricing#Tab%201)<br>「ローカルで気軽に使いたい」「CI 上で認証なしで使いたい」というユースケースに対しては、以前ほど手軽ではなくなりました。</p>
<p>そこで、認証も登録もいらない代替を探していて見つけたのが moto です。<br>(他にも、 floci や kumo といったエミュレータもありましたが、今回の記事ではスコープ外とさせていただきます)</p>
<p>この記事では、moto に対して Terraform を打ち込み、さらに「S3 → SQS → コンテナ」というイベント駆動の構成が動くところまでをローカルで検証します。</p>
<h2 id="この記事を読むとできるようになること">この記事を読むとできるようになること</h2><ul>
<li><code>docker compose up</code> で立ち上げた moto に対して、Terraform で AWS リソースを作成できる</li>
<li>S3 → SQS → ECS サービス という典型的なイベント駆動構成が moto 上で動くかどうか、自分の目で確かめられる</li>
<li>moto で「どこまで再現できて、どこからは無理か」が判断できるようになる</li>
</ul>
<h2 id="moto-とは">moto とは</h2><p>moto はもともと Python の boto3 用のユニットテスト向け mock ライブラリです。</p>
<p>なのですが、 moto には「サーバーモード」というモードも用意されています(使い方については、 サーバーモードのドキュメントを参考にしてください)。</p>
<p>サーバーモードは、Docker コンテナとして起動でき、起動すると HTTP で AWS API を受け付けるエンドポイントが立ち上がります。サーバーモードに対しては、Terraform を実行したり aws-cli を叩くことができます。</p>
<p>今回はこのサーバーモードを使います。対応サービスの一覧は公式の Implementation Coverage に記載されています。S3 &#x2F; SQS &#x2F; IAM &#x2F; ECS &#x2F; CloudWatch Logs &#x2F; DynamoDB &#x2F; Lambda など、よく使うサービスが幅広くカバーされています。</p>
<h2 id="moto-と-LocalStack-の使い分け">moto と LocalStack の使い分け</h2><p>ざっくり並べるとこんな印象です。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 moto / LocalStack"><table>
<thead>
<tr>
<th></th>
<th>moto</th>
<th>LocalStack</th>
</tr>
</thead>
<tbody><tr>
<td>アカウント登録</td>
<td><strong>不要</strong></td>
<td>必要（無料の Hobby プランでも認証トークン要）</td>
</tr>
<tr>
<td>セットアップ</td>
<td><code>docker run motoserver/moto</code></td>
<td>イメージ pull + トークン設定</td>
</tr>
<tr>
<td>商用利用</td>
<td>制限なし</td>
<td>有料プラン必須</td>
</tr>
<tr>
<td>サービス間連携の再現度</td>
<td>S3 → SQS など基本的な連携は再現可。Lambda の自動起動や Cognito の Custom Auth Flow といった連携は基本不可</td>
<td>広め。Lambda 自動起動など、moto では不可な連携も一部対応</td>
</tr>
<tr>
<td>対応サービス数 &#x2F; カバレッジ</td>
<td>主要 AWS サービスを幅広くカバー。すべて無料</td>
<td>無料の Hobby は約 30 サービス。RDS・Athena・Glue などデータ &#x2F; 分析系は有料プラン限定</td>
</tr>
</tbody></table></div>
<p>「Lambda が SQS から自動で起動する」「Cognito の状態遷移を再現する」みたいなことをしたい場合は LocalStack（有料機能を含めて）を使用する必要がありますが、「API レスポンスと、ごく一部の副作用さえあれば良い」という用途であれば、 moto で十分なケースが多そうです。</p>
<h2 id="今回検証すること">今回検証すること</h2><p>この記事では 2 つのことを試します。</p>
<ul>
<li><strong>試したいこと（1）</strong>：Terraform を moto に打ち込めるか</li>
<li><strong>試したいこと（2）</strong>：S3 → SQS → ECS サービス のイベント駆動が moto 上で動くか（副作用ありのフローも再現できるか）</li>
</ul>
<p>普段業務でよく使っている構成を参考に、以下のような構成図のインフラリソースを作成します。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-6hmlbi-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">put JSON --&gt; [S3] --event--&gt; [SQS] --ポーリング--&gt; [ECS サービス 相当のコンテナ] --&gt; ログ出力</span><br></pre></td></tr></table></figure></div>

<img fetchpriority="high" src="/images/2026/20260528a/image.png" alt="image.png" width="666" height="180">

<ul>
<li>コンテナ : SQS をポーリングし、メッセージから S3 のキーを取り出してログ出力するコンテナ</li>
</ul>
<p>動作検証を行ったリポジトリは moto-terraform に上げているので、興味がある方はご参照ください。</p>
<h2 id="準備-moto-のサーバーモードを立ち上げる">準備: moto のサーバーモードを立ち上げる</h2><p><code>docker-compose.yml</code> で <code>motoserver/moto</code> を起動します。</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">moto:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">motoserver/moto:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5050:5000&quot;</span></span><br></pre></td></tr></table></figure>

<p>この段階で <code>s3 ls</code> コマンドを叩くと、空文字列が返ってくることが確認できます。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash">docker compose up -d moto</span></span><br><span class="line"><span class="meta prompt_">% </span><span class="language-bash">aws --endpoint-url http://localhost:5050 s3 <span class="built_in">ls</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">%</span></span><br></pre></td></tr></table></figure>

<h2 id="試したいこと（1）-Terraform-を-moto-に打ち込む">試したいこと（1） : Terraform を moto に打ち込む</h2><p>ここからが本題のひとつめです。<br>Terraform の provider が向く先を moto に切り替えていきます。</p>
<h3 id="Terraform-に-moto-を打ち込むための設定">Terraform に moto を打ち込むための設定</h3><p>以下のような <code>main.tf</code> を使用する想定とします。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-6hmlbi-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-2" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># terraform/main.tf</span>
<span class="keyword">terraform</span> {
  required_version = <span class="string">&quot;&gt;= 1.0&quot;</span>
<span class="line"></span>
  required_providers {
    aws = {
      source  = <span class="string">&quot;hashicorp/aws&quot;</span>
      version = <span class="string">&quot;~&gt; 5.0&quot;</span>
    }
  }
}
<span class="line"></span>
<span class="keyword">provider</span> <span class="string">&quot;aws&quot;</span> {
  region = var.region
<span class="line"></span>
  default_tags {
    tags = {
      Environment = var.env
      ManagedBy   = <span class="string">&quot;terraform&quot;</span>
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<p>Terraform には、<code>*_override.tf</code> というファイル名で書いたリソース定義が、同名のリソースをマージで上書きしてくれる機構があります（Override Files）。</p>
<p>moto を向くための設定を <code>override.tf</code> に切り出すことで、 <code>main.tf</code> を編集することなく、moto に向く設定に上書きできます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-6hmlbi-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-3" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># terraform/override.tf</span>
<span class="keyword">variable</span> <span class="string">&quot;use_moto&quot;</span> {
  default = true
}
<span class="line"></span>
<span class="keyword">provider</span> <span class="string">&quot;aws&quot;</span> {
  region                      = <span class="string">&quot;ap-northeast-2&quot;</span>
  access_key                  = <span class="string">&quot;testing&quot;</span>
  secret_key                  = <span class="string">&quot;testing&quot;</span>
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true
  s3_use_path_style           = true
<span class="line"></span>
  endpoints {
    s3             = <span class="string">&quot;http://localhost:5050&quot;</span>
    sqs            = <span class="string">&quot;http://localhost:5050&quot;</span>
    ecs            = <span class="string">&quot;http://localhost:5050&quot;</span>
    iam            = <span class="string">&quot;http://localhost:5050&quot;</span>
    ec2            = <span class="string">&quot;http://localhost:5050&quot;</span>
    ecr            = <span class="string">&quot;http://localhost:5050&quot;</span>
    sts            = <span class="string">&quot;http://localhost:5050&quot;</span>
    cloudwatchlogs = <span class="string">&quot;http://localhost:5050&quot;</span>
  }
}</pre></td></tr></tbody></table></figure></div>

<p>特記事項は以下のとおり:</p>
<ul>
<li>ローカル完結なので <code>access_key</code> &#x2F; <code>secret_key</code> &#x2F; <code>region</code> は任意の文字列で可。</li>
<li>以下を参考に、 <code>skip_~~</code> 系を true に設定しました。<ul>
<li>Non-Python SDK’s &#x2F; Server Mode &#x2F; Example Usage</li>
</ul>
</li>
<li>ローカルだと S3 の <code>s3_use_path_style</code> を有効にしておく必要があります。これを設定しないと、ローカルでの名前解決が正しく行われません。本題から逸れるので、理由はトグルの中に入れておきます。気になる方は以下のトグルをご参照ください。</li>
</ul>
<details><summary>s3_use_path_style = true が必要な理由:</summary>

<p><strong>▪️ 2 つのアクセス URL 形式</strong></p>
<p><code>s3_use_path_style</code> とは、S3 の URL の組み立て方を「パススタイル」に強制する設定です。<br>S3 へのアクセス URL には2つの形式があります。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 形式 / URL の例"><table>
<thead>
<tr>
<th>形式</th>
<th>URL の例</th>
</tr>
</thead>
<tbody><tr>
<td>仮想ホスト形式 (デフォルト)</td>
<td>https://my-bucket.s3.amazonaws.com/key.txt</td>
</tr>
<tr>
<td>パススタイル形式</td>
<td>https://s3.amazonaws.com/my-bucket/key.txt</td>
</tr>
</tbody></table></div>
<p>仮想ホスト形式はバケット名がサブドメインに入りますが、パススタイルはパスの一部として入ります。</p>
<p><strong>▪️ なぜ moto では true が必要か</strong></p>
<p>moto は http://localhost:5050 の単一エンドポイントで動いています。仮想ホスト形式を使うと、AWS SDK は以下のような URL を組み立てようとします。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">http://my-bucket.localhost:5050/key.txt</span><br></pre></td></tr></table></figure>

<p>これは、</p>
<ul>
<li>my-bucket.localhost という名前解決ができない (DNS に存在しない)</li>
<li>moto サーバーは localhost:5050 でしか listen していない</li>
</ul>
<p>ため、接続できずに失敗します。</p>
<p>s3_use_path_style &#x3D; true にすると、以下のような URL になります。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">http://localhost:5050/my-bucket/key.txt</span><br></pre></td></tr></table></figure>

<p>これなら moto サーバーに正しく届きます。</p>
</details>

<h4 id="作成するリソース-一部抜粋">作成するリソース (一部抜粋)</h4><p>S3 バケットと SQS キューを、以下の設定で作成します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-6hmlbi-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-4" title="コードの折り返しを切り替える"></label><figcaption><span>S3</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;main&quot;</span> {
  bucket        = <span class="string">&quot;<span class="variable">${var.env}</span>-json-bucket&quot;</span>
  force_destroy = true
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket_notification&quot;</span> <span class="string">&quot;to_sqs&quot;</span> {
  bucket = aws_s3_bucket.main.id
<span class="line"></span>
  queue {
    queue_arn     = aws_sqs_queue.main.arn
    events        = [<span class="string">&quot;s3:ObjectCreated:*&quot;</span>]
    filter_suffix = <span class="string">&quot;.json&quot;</span>
  }
}</pre></td></tr></tbody></table></figure></div>

<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-6hmlbi-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-5" title="コードの折り返しを切り替える"></label><figcaption><span>SQS</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_sqs_queue&quot;</span> <span class="string">&quot;main&quot;</span> {
  name                       = <span class="string">&quot;<span class="variable">${var.env}</span>-queue&quot;</span>
  visibility_timeout_seconds = <span class="number">60</span>
  message_retention_seconds  = <span class="number">86400</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_sqs_queue_policy&quot;</span> <span class="string">&quot;allow_s3&quot;</span> {
  queue_url = aws_sqs_queue.main.id
<span class="line"></span>
  policy = jsonencode({
    Version = <span class="string">&quot;2012-10-17&quot;</span>
    Statement = [
      {
        Effect    = <span class="string">&quot;Allow&quot;</span>
        Principal = { Service = <span class="string">&quot;s3.amazonaws.com&quot;</span> }
        Action    = <span class="string">&quot;sqs:SendMessage&quot;</span>
        Resource  = aws_sqs_queue.main.arn
        Condition = {
          ArnEquals = {
            <span class="string">&quot;aws:SourceArn&quot;</span> = aws_s3_bucket.main.arn
          }
        }
      }
    ]
  })
}</pre></td></tr></tbody></table></figure></div>

<h3 id="Terraform-コマンドが実行できることを確認する">Terraform コマンドが実行できることを確認する</h3><p>ここまで揃ったら、あとは Terraform コマンドを叩くだけです。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-6hmlbi-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash"><span class="built_in">cd</span> terraform</span></span><br><span class="line"><span class="meta prompt_">% </span><span class="language-bash">terraform init</span></span><br><span class="line"><span class="meta prompt_">% </span><span class="language-bash">terraform apply -auto-approve</span></span><br><span class="line">...</span><br><span class="line">Apply complete! Resources: 17 added, 0 changed, 0 destroyed.</span><br></pre></td></tr></table></figure></div>

<p>apply が通ることを確認できました。</p>
<p>リソースが作成されたかも確認してみます。<br><code>--endpoint-url</code> で moto を向ければ、AWS CLI からリソースが見えます。<br>Terraform で定義した通りに S3 バケットと SQS キューが作られていることが確認できました。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-6hmlbi-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash">aws --endpoint-url http://localhost:5050 s3 <span class="built_in">ls</span></span></span><br><span class="line">2026-04-07 22:30:11 moto-test-json-bucket</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">% </span><span class="language-bash">aws --endpoint-url http://localhost:5050 --region ap-northeast-2 sqs list-queues</span></span><br><span class="line">QueueUrls:</span><br><span class="line">- http://localhost:5050/123456789012/moto-test-queue</span><br></pre></td></tr></table></figure></div>

<h2 id="試したいこと（2）-S3-→-SQS-→-コンテナ-のイベント駆動を動かす">試したいこと（2） : S3 → SQS → コンテナ のイベント駆動を動かす</h2><p>次は副作用ありのフローを試します。<br>S3 にオブジェクトが作成されたら自動的に SQS にイベント通知が飛び、それを ECS サービスが拾って後段で処理する、という構成を moto 上で動かします。</p>
<img src="/images/2026/20260528a/image_2.png" alt="image.png" width="666" height="180" loading="lazy">

<p>S3 のドキュメント には「ObjectCreated された際の SQS への通知をサポートしている」旨の記載があるため、 Put 時の SQS への通知も飛んでくれるはずです。<br>これを、実際に動かして検証してみます。</p>
<blockquote>
<p>The configuration can be persisted, but at the moment we only send notifications to the following targets:</p>
<ul>
<li>AWSLambda</li>
<li>SNS</li>
<li>SQS</li>
<li>EventBridge</li>
</ul>
<p>For the following events:</p>
<ul>
<li>s3:ObjectCreated:CompleteMultipartUpload</li>
<li>s3:ObjectCreated:Copy</li>
<li>s3:ObjectCreated:Post</li>
<li>s3:ObjectCreated:Put</li>
<li>s3:ObjectDeleted</li>
<li>s3:ObjectRestore:Post</li>
</ul>
</blockquote>
<h3 id="S3-→-SQS-の通知設定">S3 → SQS の通知設定</h3><p>Terraform で、以下のような S3 イベント通知を設定します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-6hmlbi-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-8" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># terraform/s3.tf</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;main&quot;</span> {
  bucket        = <span class="string">&quot;<span class="variable">${var.env}</span>-json-bucket&quot;</span>
  force_destroy = true
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket_notification&quot;</span> <span class="string">&quot;to_sqs&quot;</span> {
  bucket = aws_s3_bucket.main.id
<span class="line"></span>
  queue {
    queue_arn     = aws_sqs_queue.main.arn
    events        = [<span class="string">&quot;s3:ObjectCreated:*&quot;</span>]
    filter_suffix = <span class="string">&quot;.json&quot;</span>
  }
}</pre></td></tr></tbody></table></figure></div>

<p>「<code>.json</code> で終わるオブジェクトが作られたら SQS にイベントを送る」だけのシンプルな設定です。<br>これがちゃんと moto 上で副作用として発火するのかが、ここでの検証ポイントになります。</p>
<h3 id="ポーリング用のコンテナを起動する">ポーリング用のコンテナを起動する</h3><p>今回は <code>docker-compose.yml</code> でコンテナを作成し、このコンテナを ECS サービスの代わりとしました。</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">moto:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">motoserver/moto:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5050:5000&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">task2:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">./apps/task2</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">moto</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">AWS_ENDPOINT_URL=http://moto:5000</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">AWS_ACCESS_KEY_ID=testing</span></span><br><span class="line">      <span class="string">...</span></span><br></pre></td></tr></table></figure>

<div class="note-container note-warn"><span class="note-icon"></span><div>

<p>moto の ECS は API のレスポンスを返すだけで、実際にコンテナを起動してくれるものではありません。<br>したがって、 ECS サービスやタスクを模したい場合は、 moto の外側で動かす必要があります。</p>
<p>最初は、上記のコンテナも ECS のエミュレータ上で動かせたら嬉しいと考えていたのですが、上記の制約より、普通のコンテナとして動かす方針で検証を進めました。</p>
</div></div>

<h4 id="動かしてみる">動かしてみる</h4><p>初期状態のキュー内のメッセージは当然 0 個です。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-6hmlbi-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash">aws --endpoint-url http://localhost:5050 sqs get-queue-attributes \</span></span><br><span class="line"><span class="language-bash">  --queue-url http://localhost:5050/123456789012/moto-test-queue \</span></span><br><span class="line"><span class="language-bash">  --attribute-names ApproximateNumberOfMessages \</span></span><br><span class="line"><span class="language-bash">  --region ap-northeast-2</span></span><br><span class="line">Attributes:</span><br><span class="line">  ApproximateNumberOfMessages: &#x27;0&#x27;</span><br></pre></td></tr></table></figure></div>

<p>この状態でS3 に JSON を put してみます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-6hmlbi-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash">S3_KEY=<span class="string">&quot;data/<span class="subst">$(date +%Y%m%d-%H%M%S)</span>.json&quot;</span></span></span><br><span class="line"><span class="meta prompt_">% </span><span class="language-bash"><span class="built_in">echo</span> <span class="string">&#x27;&#123;&quot;hello&quot;: &quot;world!&quot;&#125;&#x27;</span> | aws --endpoint-url http://localhost:5050 s3 <span class="built_in">cp</span> - \</span></span><br><span class="line"><span class="language-bash">  <span class="string">&quot;s3://moto-test-json-bucket/<span class="variable">$&#123;S3_KEY&#125;</span>&quot;</span> \</span></span><br><span class="line"><span class="language-bash">  --region ap-northeast-2</span></span><br></pre></td></tr></table></figure></div>

<p>その後ふたたびキュー内のメッセージ数を見てみると、1 個になっていることが分かります。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-6hmlbi-11" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-11" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash">aws --endpoint-url http://localhost:5050 sqs get-queue-attributes \</span></span><br><span class="line"><span class="language-bash">  --queue-url http://localhost:5050/123456789012/moto-test-queue \</span></span><br><span class="line"><span class="language-bash">  --attribute-names ApproximateNumberOfMessages \</span></span><br><span class="line"><span class="language-bash">  --region ap-northeast-2</span></span><br><span class="line">Attributes:</span><br><span class="line">  ApproximateNumberOfMessages: &#x27;1&#x27;</span><br></pre></td></tr></table></figure></div>

<p>ドキュメント通り、S3 イベントが発火し、キューにメッセージがプッシュされたのを確認できました。</p>
<p>さらに、キューからメッセージをポーリングして、ポーリングした JSON をログ出力するコンテナを立ち上げます。<br>すると、立ち上げたコンテナで以下のようなログが出力されました。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-6hmlbi-12" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-6hmlbi-12" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">moto-terraform-ecs-task2-1  | 2026/05/24 06:26:22 queue URL: http://moto:5000/123456789012/moto-test-queue</span><br><span class="line">moto-terraform-ecs-task2-1  | 2026/05/24 06:26:22 start polling SQS...</span><br><span class="line">moto-terraform-ecs-task2-1  | 2026/05/24 06:26:41 skipping message: no records <span class="keyword">in</span> S3 event (skipping non-S3 message)</span><br><span class="line">moto-terraform-ecs-task2-1  | 2026/05/24 06:36:15 fetching s3://moto-test-json-bucket/data/20260524-153609.json</span><br><span class="line">moto-terraform-ecs-task2-1  | 2026/05/24 06:36:15 received JSON: &#123;<span class="string">&quot;hello&quot;</span>: <span class="string">&quot;world!&quot;</span>&#125;</span><br></pre></td></tr></table></figure></div>

<p>無事、 S3 → put event → SQS → コンテナでポーリング の流れが動くことを確認できました。</p>
<h2 id="moto-を使用する際の制約">moto を使用する際の制約</h2><p>ここまで動いたとはいえ、moto は実 AWS の完全な置き換えではありません。今回検証して見えた範囲だと、以下のような制約があります。</p>
<ul>
<li><strong>状態遷移系は再現対象外</strong><br>moto は Lambda 関数自体は Docker コンテナで実行できますが、他サービスのトリガー機構の中で Lambda が呼び出されるフローは実装が限定的です。たとえば Cognito の Custom Auth Flow (認証チャレンジを Lambda で進める仕組み) は moto の Cognito 側にトリガー発火実装が無く動きません。</li>
<li><strong>副作用ありの動作は限定的</strong><br>S3 → SQS は動きますが、たとえば SQS → Lambda の自動起動は moto では発火しません。</li>
</ul>
<p>「ECS タスクの実体まで含めた E2E が要る」であったり、「Lambda の自動実行で繋がる pipeline を試したい」となると、LocalStack（場合によっては有料機能）や実 AWS が必要になります。逆に「API レスポンスの整合性」と「S3 → SQS のような限定的な副作用」さえあれば十分、というケースなら moto はかなり気軽に使えます。</p>
<p>どのような API および副作用が実装されているかはドキュメントにかなり詳しくまとまっているので、テストしたい AWS・Terraform 操作を満たす API が moto に実装されているかを確認してから moto の採用を決定するのが良いと思います。</p>
<h2 id="まとめ">まとめ</h2><p>この記事では moto に対して Terraform を打ち込み、S3 → SQS → ECS サービス (相当のコンテナ) というイベント駆動の構成をローカル完結で動かしてみました。</p>
<p>moto を使用する場合、 ECS タスクの実コンテナ起動や Lambda の自動実行といった「実体を伴う動作」の再現に限界はあります。<br>一方で、認証も不要で <code>docker compose up</code> だけで立ち上がる軽さは大きな利点です。今回は CI 上での検証は行なっていませんが、「CI 上で、 Terraform コマンドが正しいかのチェックや、簡単な AWS 処理のテストをしたい」といったニーズに一致しているように感じました。</p>
<p>LocalStack の Community Edition 廃止以降、「無料で気軽に AWS をエミュレートしたい」というニーズに対する現実的な選択肢として、moto は十分検討に値する印象でした。</p>
<p>無料で認証なしで使えるので、皆さんもぜひ気軽に触ってみてください。</p>
]]></content>
    <summary type="html">Terraform を打ち込むことができる AWS エミュレータの「moto」を紹介します。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="SQS" scheme="https://future-architect.github.io/tags/SQS/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="コンテナ" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A/"/>
    <category term="モック" scheme="https://future-architect.github.io/tags/%E3%83%A2%E3%83%83%E3%82%AF/"/>
  </entry>
  <entry>
    <title>Terraform で AWS SAM CLI を利用して、ローカルで Lambda のテストとデプロイを試してみた</title>
    <link href="https://future-architect.github.io/articles/20260527a/"/>
    <id>https://future-architect.github.io/articles/20260527a/</id>
    <published>2026-05-26T15:00:00.000Z</published>
    <updated>2026-05-26T15:00:00.000Z</updated>
    <author><name>香村真紀</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260527a/top.jpg" alt="" width="800" height="446">

<h2 id="はじめに">はじめに</h2><p>こんにちは、香村真紀です。本記事は Terraform連載 2026 の掲載記事です。</p>
<p>AWS SAM CLI は Docker を使って Lambda 実行環境をローカルに再現するため、AWS へデプロイせずに関数の動作確認ができます。Terraform 構成のまま使える点が便利だったので、ローカルテストとデプロイの方法を紹介します。</p>
<h2 id="前提条件">前提条件</h2><ul>
<li>AWS CLI v2.34.41</li>
<li>Terraform v1.14.3</li>
<li>Go v1.25.3</li>
<li>Docker v29.1.3（<code>sam local</code> の実行に必要）</li>
<li>AWS SAM CLI v1.161.0</li>
</ul>
<h2 id="SAM-でできること">SAM でできること</h2><p>AWS 公式ドキュメント</p>
<blockquote>
<p>AWS SAM CLI とは AWS CloudFormation テンプレートで使用する場合に最適です。Terraform などのサードパーティー製品とも連携します。</p>
<ul>
<li>新しいアプリケーションプロジェクトを迅速に初期化します</li>
<li>デプロイ用にアプリケーションを構築します</li>
<li>ローカルでのデバッグとテストを実行します</li>
<li>アプリケーションをデプロイします</li>
<li>CI&#x2F;CD デプロイパイプラインを設定します</li>
<li>クラウド内のアプリケーションをモニタリングおよびトラブルシューティングします</li>
<li>開発中にローカルの変更をクラウドに同期します</li>
</ul>
</blockquote>
<p>※ この記事では「ローカルでのデバッグとテスト」と「デプロイ」を中心に紹介します。</p>
<h2 id="AWS-SAM-CLI-で-Lambda-関数のプロジェクトを自動生成">AWS SAM CLI で Lambda 関数のプロジェクトを自動生成</h2><p>AWS SAM（Serverless Application Model）を使って、Lambda 関数のサンプルプロジェクトを自動生成しました。</p>
<ul>
<li>テンプレート: Hello World（最もシンプルなもの）</li>
<li>言語: Go &#x2F; provided.al2023</li>
</ul>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># AWS SAM CLI をインストール</span></span><br><span class="line">brew install aws-sam-cli　</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. プロジェクト生成</span></span><br><span class="line">sam init</span><br><span class="line"><span class="comment"># 2. ビルド</span></span><br><span class="line">sam build</span><br><span class="line"><span class="comment"># 3. ローカルテスト</span></span><br><span class="line">sam <span class="built_in">local</span> invoke</span><br><span class="line"><span class="comment"># 4. AWS にデプロイ</span></span><br><span class="line">sam deploy</span><br></pre></td></tr></table></figure>

<figure class="highlight sh"><figcaption><span>生成されたファイル</span></figcaption><table><tr><td class="code"><pre><span class="line">➜  sam-app tree</span><br><span class="line">.</span><br><span class="line">├── Makefile</span><br><span class="line">├── README.md</span><br><span class="line">├── events</span><br><span class="line">│   └── event.json</span><br><span class="line">├── hello-world</span><br><span class="line">│   ├── go.mod</span><br><span class="line">│   ├── go.sum</span><br><span class="line">│   ├── main.go</span><br><span class="line">│   └── main_test.go</span><br><span class="line">├── samconfig.toml</span><br><span class="line">└── template.yaml</span><br></pre></td></tr></table></figure>

<p><strong>各コマンドの解説</strong></p>
<ol>
<li><code>sam build</code><br>Go バイナリをクロスコンパイルして、成果物を <code>.aws-sam/build/</code> に格納します。</li>
<li><code>sam local invoke HelloWorldFunction</code><br>Docker を使って自分の Mac 上に Lambda の実行環境を再現し、実際に関数を動かして確認します。</li>
</ol>
<div class="code-block"><figure class="highlight json"><input type="checkbox" id="code-wrap-14nmayy-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;statusCode&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span> <span class="attr">&quot;body&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;\&quot;message\&quot;: \&quot;hello world\&quot;&#125;&quot;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div>

<ol start="3">
<li><code>sam deploy --guided</code><br>実際の AWS 環境に以下のリソースを作成します。</li>
</ol>
<div class="scroll" tabindex="0" role="region" aria-label="表 サービス / リソース名 / 役割"><table>
<thead>
<tr>
<th>サービス</th>
<th>リソース名</th>
<th>役割</th>
</tr>
</thead>
<tbody><tr>
<td>AWS Lambda</td>
<td>hello-world 関数</td>
<td>処理を担当</td>
</tr>
<tr>
<td>API Gateway</td>
<td>ServerlessRestApi</td>
<td>HTTP エンドポイントを提供。外部からのリクエストを Lambda に渡す</td>
</tr>
<tr>
<td>IAM Role</td>
<td>sam-app-HelloWorldFunctionRole-…</td>
<td>Lambda が AWS サービスを使う際の権限設定</td>
</tr>
<tr>
<td>S3</td>
<td>aws-sam-cli-managed-default-…</td>
<td>デプロイ用のコードを一時的に保管するバケット</td>
</tr>
<tr>
<td>CloudFormation</td>
<td>スタック名: sam-app</td>
<td>上記すべてのリソースをまとめて管理する仕組み</td>
</tr>
</tbody></table></div>
<div class="code-block"><figure class="highlight txt"><input type="checkbox" id="code-wrap-14nmayy-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-2" title="コードの折り返しを切り替える"></label><figcaption><span>作成された API エンドポイント（サンプル）</span></figcaption><table><tr><td class="code"><pre><span class="line">https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/</span><br></pre></td></tr></table></figure></div>

<h3 id="AWS-リソースを削除する場合">AWS リソースを削除する場合</h3><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> sam-app</span><br><span class="line">sam delete</span><br></pre></td></tr></table></figure>

<div class="scroll" tabindex="0" role="region" aria-label="表 削除されるもの / リソース名"><table>
<thead>
<tr>
<th>削除されるもの</th>
<th>リソース名</th>
</tr>
</thead>
<tbody><tr>
<td>Lambda 関数</td>
<td>sam-app-HelloWorldFunction-…</td>
</tr>
<tr>
<td>API Gateway</td>
<td>ServerlessRestApi</td>
</tr>
<tr>
<td>IAM Role</td>
<td>sam-app-HelloWorldFunctionRole-…</td>
</tr>
<tr>
<td>CloudFormation スタック</td>
<td>sam-app</td>
</tr>
</tbody></table></div>
<p>注意：S3 バケット（aws-sam-cli-managed-default-…）は削除するか確認されます。</p>
<h2 id="Terraform-SAM-CLI-構成で作り直す">Terraform + SAM CLI 構成で作り直す</h2><p>AWS 公式ドキュメント</p>
<h3 id="SAM-単体との構成比較">SAM 単体との構成比較</h3><div class="scroll" tabindex="0" role="region" aria-label="表 ファイル &#x2F; ディレクトリ / SAM 単体 / Terraform + SAM CLI"><table>
<thead>
<tr>
<th>ファイル &#x2F; ディレクトリ</th>
<th>SAM 単体</th>
<th>Terraform + SAM CLI</th>
</tr>
</thead>
<tbody><tr>
<td><code>template.yaml</code></td>
<td>✅ 必要（手書き）</td>
<td>❌ 不要（Terraform の <code>.tf</code> ファイルが代わり）</td>
</tr>
<tr>
<td><code>samconfig.toml</code></td>
<td>✅ 必要</td>
<td>❌ 不要（<code>sam deploy</code> を使わないため）</td>
</tr>
</tbody></table></div>
<h3 id="初期構成の方針">初期構成の方針</h3><p><code>sam init</code> は SAM 単体（<code>template.yaml</code> ベース）のプロジェクトを生成するためのコマンドなので、今回の構成では使いません。</p>
<p><strong>作るもの</strong>:</p>
<ul>
<li>Lambda 関数（Go &#x2F; provided.al2023）</li>
<li>IAM ロール</li>
</ul>
<p><strong>ローカルテスト（SAM CLI）</strong>:</p>
<ul>
<li><code>sam local invoke --hook-name terraform</code> で Lambda を直接呼ぶ</li>
</ul>
<h3 id="Step-1：構成を考える">Step 1：構成を考える</h3><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sam-tf-app/</span><br><span class="line">├── <span class="built_in">functions</span>/</span><br><span class="line">│   └── hello/</span><br><span class="line">│       ├── main.go</span><br><span class="line">│       └── go.mod</span><br><span class="line">├── events/</span><br><span class="line">│   └── event.json</span><br><span class="line">└── terraform/</span><br><span class="line">    ├── provider.tf</span><br><span class="line">    ├── main.tf</span><br><span class="line">    └── iam.tf</span><br></pre></td></tr></table></figure>

<h3 id="Step-2：Lambda-のコードを書く">Step 2：Lambda のコードを書く</h3><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> <span class="built_in">functions</span>/hello</span><br><span class="line">go mod init hello</span><br><span class="line">go get github.com/aws/aws-lambda-go/lambda</span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-14nmayy-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// main.go（初期版）</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;context&quot;</span></span><br><span class="line">    <span class="string">&quot;github.com/aws/aws-lambda-go/lambda&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Response <span class="keyword">struct</span> &#123;</span><br><span class="line">    StatusCode <span class="type">int</span>    <span class="string">`json:&quot;statusCode&quot;`</span></span><br><span class="line">    Body       <span class="type">string</span> <span class="string">`json:&quot;body&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">handler</span><span class="params">(ctx context.Context)</span></span> (Response, <span class="type">error</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> Response&#123;StatusCode: <span class="number">200</span>, Body: <span class="string">`&#123;&quot;message&quot;: &quot;hello world&quot;&#125;`</span>&#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    lambda.Start(handler)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h3 id="Step-3：Terraform-ファイルを書く">Step 3：Terraform ファイルを書く</h3><p><code>provider.tf</code>（リージョン・プロバイダー設定）と <code>iam.tf</code>（Lambda 実行ロール）はこの記事では省略します。</p>
<p>ポイントになるのは <code>main.tf</code> です。</p>
<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-14nmayy-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-4" title="コードの折り返しを切り替える"></label><figcaption><span>main.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  project_root    = abspath(<span class="string">&quot;<span class="variable">${path.module}</span>/..&quot;</span>)
  lambda_src_path = <span class="string">&quot;<span class="variable">${local.project_root}</span>/functions/hello&quot;</span>
  build_output    = <span class="string">&quot;<span class="variable">${local.project_root}</span>/.build&quot;</span>
}
<span class="line"></span>
<span class="comment"># ① Go のビルド（GOOS=linux が必須）</span>
<span class="keyword">resource</span> <span class="string">&quot;null_resource&quot;</span> <span class="string">&quot;build_hello&quot;</span> {
  triggers = {
    always_run = timestamp()
  }
<span class="line"></span>
  provisioner <span class="string">&quot;local-exec&quot;</span> {
    command = &lt;&lt;-EOT
      mkdir -p ${local.build_output}
      cd ${local.lambda_src_path} &amp;&amp; \
      GOOS=linux GOARCH=amd64 go build -o ${local.build_output}/bootstrap .
      chmod +x ${local.build_output}/bootstrap
    EOT
  }
}
<span class="line"></span>
<span class="comment"># ② デプロイ用に zip 化</span>
<span class="keyword">data</span> <span class="string">&quot;archive_file&quot;</span> <span class="string">&quot;hello&quot;</span> {
  type        = <span class="string">&quot;zip&quot;</span>
  source_file = <span class="string">&quot;<span class="variable">${local.build_output}</span>/bootstrap&quot;</span>
  output_path = <span class="string">&quot;<span class="variable">${local.build_output}</span>/hello.zip&quot;</span>
  depends_on  = [null_resource.build_hello]
}
<span class="line"></span>
<span class="comment"># ③ SAM CLI への案内板（これがないと sam local invoke が動かない）</span>
<span class="keyword">resource</span> <span class="string">&quot;null_resource&quot;</span> <span class="string">&quot;sam_metadata_aws_lambda_function_hello&quot;</span> {
  triggers = {
    resource_name        = <span class="string">&quot;aws_lambda_function.hello&quot;</span>
    resource_type        = <span class="string">&quot;ZIP_LAMBDA_FUNCTION&quot;</span>
    original_source_code = local.lambda_src_path
    built_output_path    = local.build_output
  }
  depends_on = [null_resource.build_hello]
}
<span class="line"></span>
<span class="comment"># ④ Lambda 関数本体</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_lambda_function&quot;</span> <span class="string">&quot;hello&quot;</span> {
  filename         = <span class="keyword">data</span>.archive_file.hello.output_path
  function_name    = <span class="string">&quot;hello-function&quot;</span>
  handler          = <span class="string">&quot;bootstrap&quot;</span>
  runtime          = <span class="string">&quot;provided.al2023&quot;</span>
  role             = aws_iam_role.lambda.arn
  source_code_hash = <span class="keyword">data</span>.archive_file.hello.output_base64sha256
  depends_on       = [null_resource.build_hello]
}</pre></td></tr></tbody></table></figure></div>

<p>③ の <code>null_resource.sam_metadata_aws_lambda_function_hello</code> が SAM CLI に Lambda の場所を伝える案内板です。これがないと <code>sam build --hook-name terraform</code> が Lambda を認識できません。</p>
<h3 id="Step-4：コマンドを実行">Step 4：コマンドを実行</h3><p>コマンドを実行する前に、<code>sam build --hook-name terraform</code> が何をしているかを理解しておくと動作のイメージがつかみやすくなります。</p>
<ol>
<li><strong>Terraform でインフラを解析</strong><br><code>terraform plan</code> を実行して Lambda 関数の情報（ソースの場所・ランタイム・アーキテクチャ等）を JSON として取得する</li>
<li><strong>SAM 用の内部テンプレートを生成</strong><br>JSON から <code>template.json</code> を作成し、SAM CLI が Lambda を認識できる形に変換する（<code>template.yaml</code> の代わり）</li>
<li><strong>Go バイナリをビルド</strong><br><code>GOOS=linux GOARCH=amd64 go build</code> で Lambda 実行環境（Linux&#x2F;x86_64）向けにクロスコンパイルする</li>
<li><strong>バイナリを SAM のビルドディレクトリにコピー</strong><br><code>.aws-sam/build/</code> に配置することで <code>sam local invoke</code> が使える状態にする。</li>
</ol>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-14nmayy-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># 生成されるファイル</span></span><br><span class="line">terraform/</span><br><span class="line">├── .aws-sam-iacs/           <span class="comment"># SAM CLI の中間ファイル（通常は触らない）</span></span><br><span class="line">│   └── iacs_metadata/</span><br><span class="line">│       ├── template.json    <span class="comment"># Terraform から生成された内部テンプレート</span></span><br><span class="line">│       └── Makefile</span><br><span class="line">└── .aws-sam/</span><br><span class="line">    └── build/</span><br><span class="line">        ├── template.yaml                  <span class="comment"># SAM CLI が読む内部テンプレート（自動生成）</span></span><br><span class="line">        └── AwsLambdaFunctionHello.../</span><br><span class="line">            └── bootstrap                  <span class="comment"># クロスコンパイル済みバイナリ</span></span><br></pre></td></tr></table></figure></div>

<p>SAM 単体の <code>sam build</code> との違いは以下の通りです。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 比較項目 / SAM 単体の sam build / Terraform + sam build --hook-name"><table>
<thead>
<tr>
<th>比較項目</th>
<th>SAM 単体の <code>sam build</code></th>
<th>Terraform + <code>sam build --hook-name terraform</code></th>
</tr>
</thead>
<tbody><tr>
<td>インフラ定義の読み込み元</td>
<td><code>template.yaml</code></td>
<td><code>main.tf</code></td>
</tr>
<tr>
<td>内部テンプレートの生成</td>
<td>不要（<code>template.yaml</code> がそのまま使われる）</td>
<td>✅ 自動生成（<code>.aws-sam-iacs/iacs_metadata/template.json</code>）</td>
</tr>
<tr>
<td>ビルドコマンド</td>
<td>SAM が自動で実行</td>
<td><code>null_resource</code> の <code>provisioner &quot;local-exec&quot;</code> 経由で実行</td>
</tr>
<tr>
<td><code>sam_metadata</code></td>
<td>不要</td>
<td>✅ 必須（Lambda の場所を SAM CLI に伝える案内板）</td>
</tr>
<tr>
<td><code>template.yaml</code></td>
<td>✅ 手書きが必要</td>
<td>❌ 不要</td>
</tr>
<tr>
<td>ビルド成果物の格納先</td>
<td><code>.aws-sam/build/</code></td>
<td><code>.aws-sam/build/</code>（同じ）</td>
</tr>
<tr>
<td>ローカル実行コマンド</td>
<td><code>sam local invoke 関数名</code></td>
<td><code>sam local invoke --hook-name terraform リソース名</code></td>
</tr>
</tbody></table></div>
<h4 id="実際のコマンド">実際のコマンド</h4><div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-14nmayy-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># Go の依存解決（functions/hello/ で実行）</span></span><br><span class="line">go mod tidy</span><br><span class="line"></span><br><span class="line"><span class="comment"># Terraform の初期化（terraform/ で実行）</span></span><br><span class="line">terraform init</span><br><span class="line"></span><br><span class="line"><span class="comment"># ビルド（terraform/ で実行）</span></span><br><span class="line">sam build --hook-name terraform --terraform-project-root-path ..</span><br><span class="line"></span><br><span class="line"><span class="comment"># ローカルテスト（terraform/ で実行）</span></span><br><span class="line">sam <span class="built_in">local</span> invoke --hook-name terraform aws_lambda_function.hello -e ../events/event.json</span><br><span class="line"></span><br><span class="line"><span class="comment"># AWS にデプロイ（terraform/ で実行）</span></span><br><span class="line">terraform apply</span><br></pre></td></tr></table></figure></div>

<h4 id="ローカルテストの実行例">ローカルテストの実行例</h4><p><code>events/event.json</code>（<code>sam local invoke</code> に渡すイベント）：</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;httpMethod&quot;</span><span class="punctuation">:</span> <span class="string">&quot;GET&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/hello&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;queryStringParameters&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;headers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;Accept&quot;</span><span class="punctuation">:</span> <span class="string">&quot;application/json&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Content-Type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;application/json&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;pathParameters&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;requestContext&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;resourcePath&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/hello&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;httpMethod&quot;</span><span class="punctuation">:</span> <span class="string">&quot;GET&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;stg&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;body&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;isBase64Encoded&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-14nmayy-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># terraform/ で実行</span></span><br><span class="line">sam <span class="built_in">local</span> invoke --hook-name terraform aws_lambda_function.hello -e ../events/event.json</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight json"><input type="checkbox" id="code-wrap-14nmayy-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;statusCode&quot;</span><span class="punctuation">:</span><span class="number">200</span><span class="punctuation">,</span><span class="attr">&quot;body&quot;</span><span class="punctuation">:</span><span class="string">&quot;&#123;\&quot;message\&quot;: \&quot;hello world\&quot;&#125;&quot;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div>

<h2 id="API-Gateway-を追加する">API Gateway を追加する</h2><h3 id="ファイル構成の更新">ファイル構成の更新</h3><div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-14nmayy-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">sam-tf-app/</span><br><span class="line">├── <span class="built_in">functions</span>/</span><br><span class="line">│   └── hello/</span><br><span class="line">│       ├── main.go        <span class="comment"># API Gateway 対応に変更</span></span><br><span class="line">│       └── go.mod</span><br><span class="line">├── events/</span><br><span class="line">│   └── event.json</span><br><span class="line">└── terraform/</span><br><span class="line">    ├── provider.tf</span><br><span class="line">    ├── main.tf</span><br><span class="line">    ├── iam.tf</span><br><span class="line">    └── api_gateway.tf     <span class="comment"># 追加</span></span><br></pre></td></tr></table></figure></div>

<h3 id="main-go-を-API-Gateway-対応に変更">main.go を API Gateway 対応に変更</h3><p>API Gateway のリクエスト&#x2F;レスポンス型を使うよう更新します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">go get github.com/aws/aws-lambda-go/events</span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-14nmayy-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// main.go（API Gateway 対応版）</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;context&quot;</span></span><br><span class="line">    <span class="string">&quot;github.com/aws/aws-lambda-go/events&quot;</span></span><br><span class="line">    <span class="string">&quot;github.com/aws/aws-lambda-go/lambda&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">handler</span><span class="params">(ctx context.Context, req events.APIGatewayProxyRequest)</span></span> (events.APIGatewayProxyResponse, <span class="type">error</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> events.APIGatewayProxyResponse&#123;</span><br><span class="line">        StatusCode: <span class="number">200</span>,</span><br><span class="line">        Body:       <span class="string">`&#123;&quot;message&quot;: &quot;hello world&quot;&#125;`</span>,</span><br><span class="line">    &#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    lambda.Start(handler)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h3 id="api-gateway-tf-を作成">api_gateway.tf を作成</h3><p>SAM 単体では <code>template.yaml</code> の <code>Events:</code> 数行で済む部分を、Terraform では明示的に書く必要があります。<br>全コードはこの記事では省略します。</p>
<p>主なリソースは以下の通りです。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 リソース / 役割"><table>
<thead>
<tr>
<th>リソース</th>
<th>役割</th>
</tr>
</thead>
<tbody><tr>
<td><code>aws_api_gateway_rest_api</code></td>
<td>API 本体</td>
</tr>
<tr>
<td><code>aws_api_gateway_resource</code></td>
<td><code>/hello</code> パスの定義</td>
</tr>
<tr>
<td><code>aws_api_gateway_method</code></td>
<td>GET メソッドの定義</td>
</tr>
<tr>
<td><code>aws_api_gateway_integration</code></td>
<td>Lambda との接続（AWS_PROXY 方式）</td>
</tr>
<tr>
<td><code>aws_api_gateway_deployment</code></td>
<td>API のデプロイ</td>
</tr>
<tr>
<td><code>aws_api_gateway_stage</code></td>
<td>ステージ（stg）の設定</td>
</tr>
<tr>
<td><code>aws_lambda_permission</code></td>
<td>API Gateway が Lambda を呼ぶ権限</td>
</tr>
</tbody></table></div>
<p>特に重要なのは <code>aws_lambda_permission</code> です。これがないと API Gateway から Lambda を呼び出せません。</p>
<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-14nmayy-11" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-11" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_lambda_permission&quot;</span> <span class="string">&quot;api_gateway&quot;</span> {
  statement_id  = <span class="string">&quot;AllowAPIGatewayInvoke&quot;</span>
  action        = <span class="string">&quot;lambda:InvokeFunction&quot;</span>
  function_name = aws_lambda_function.hello.function_name
  principal     = <span class="string">&quot;apigateway.amazonaws.com&quot;</span>
  source_arn    = <span class="string">&quot;<span class="variable">${aws_api_gateway_rest_api.hello.execution_arn}</span>/*/*&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<h3 id="ビルドとデプロイ">ビルドとデプロイ</h3><div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-14nmayy-12" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-14nmayy-12" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># terraform/ で実行</span></span><br><span class="line">sam build --hook-name terraform --terraform-project-root-path ..</span><br><span class="line">terraform apply</span><br><span class="line"></span><br><span class="line"><span class="comment"># エンドポイント確認</span></span><br><span class="line">terraform output api_endpoint</span><br><span class="line"><span class="comment"># &quot;https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/stg/hello&quot;</span></span><br><span class="line"></span><br><span class="line">curl $(terraform output -raw api_endpoint)</span><br><span class="line"><span class="comment"># &#123;&quot;message&quot;: &quot;hello world&quot;&#125;</span></span><br></pre></td></tr></table></figure></div>

<h3 id="API-Gateway-のローカルテスト">API Gateway のローカルテスト</h3><p><code>sam local start-api</code> を使うと、ローカルで API Gateway + Lambda の動作を確認できます。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># terraform/ で実行（Ctrl+C で停止）</span></span><br><span class="line">sam <span class="built_in">local</span> start-api --hook-name terraform</span><br></pre></td></tr></table></figure>

<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 別ターミナルで</span></span><br><span class="line">curl http://127.0.0.1:3000/hello</span><br><span class="line"><span class="comment"># &#123;&quot;message&quot;: &quot;hello world&quot;&#125;</span></span><br></pre></td></tr></table></figure>

<p><code>sam local invoke</code> との違いは、<code>start-api</code> はサーバーを起動したままにするため、ブラウザや curl で何度でもリクエストを送れる点です。</p>
<h3 id="AWS-リソースを削除する場合-1">AWS リソースを削除する場合</h3><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># terraform/ で実行</span></span><br><span class="line">terraform destroy</span><br></pre></td></tr></table></figure>

<h2 id="さいごに">さいごに</h2><p>Terraform + AWS SAM CLI を使うことで、AWS へデプロイせずに Lambda の動作確認ができました。</p>
<p>コードを他のコードと同じリポジトリで一元管理できるようになり、EOL や脆弱性管理のフローにも乗せやすくなります。<br>Terraform で Lambda を管理している方の参考になれば幸いです。</p>
<h2 id="参考">参考</h2><ul>
<li>AWS SAM CLI とは（公式）</li>
<li>AWS SAM CLI Terraform サポート（公式）</li>
<li>Terraform と連携させた AWS SAM CLI をローカルでのデバッグおよびテストに使用する方法（公式）</li>
</ul>
]]></content>
    <summary type="html">AWS SAM CLI は Docker を使って Lambda 実行環境をローカルに再現するため、AWS へデプロイせずに関数の動作確認ができます。Terraform 構成のまま使える点が便利だったので、ローカルテストとデプロイの方法を紹介します。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="CLI" scheme="https://future-architect.github.io/tags/CLI/"/>
    <category term="Lambda" scheme="https://future-architect.github.io/tags/Lambda/"/>
    <category term="SAM" scheme="https://future-architect.github.io/tags/SAM/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="テスト" scheme="https://future-architect.github.io/tags/%E3%83%86%E3%82%B9%E3%83%88/"/>
  </entry>
  <entry>
    <title>Terraform × Policy as Code:管理策を実装し、追跡可能なガードレールへ</title>
    <link href="https://future-architect.github.io/articles/20260526a/"/>
    <id>https://future-architect.github.io/articles/20260526a/</id>
    <published>2026-05-25T15:00:00.000Z</published>
    <updated>2026-05-25T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260526a/top.jpg" alt="" width="800" height="446">

<blockquote>
<p>「PaC は管理策を実装するのである。通常、PaC が実装する管理は、組織が作成または採用した標準に由来する。これらの統制は、追跡可能で監査可能でなければならない。」</p>
<p>出典: Policy as Code (O’Reilly Media)</p>
</blockquote>
<h2 id="1-はじめに">1. はじめに</h2><p>こんにちは、棚井龍之介です。本記事は Terraform 連載 2026 の5本目です。</p>
<p>Terraform のコードを Claude Code をはじめとした生成 AI に書かせる場面が、現場でも増えているのではないかと想像しています。私自身は Terraform からしばらく離れていますが、「VPC と RDS と CloudTrail を組んでほしい」と自然言語で頼めば、<code>.tf</code> ファイル一式が数秒で出てくる、というのは想像に難くありません。公式ドキュメントを開く時間より、AI に質問して出力を読む時間のほうが長い、というエンジニアも珍しくないでしょう。</p>
<p>そこで気になるのは、どうやってレビューするのか、という点です。</p>
<p>人間のレビュアーは完璧ではありませんし、そもそもインフラ規模の拡大に対してレビュアー数は増えてくれません。<code>Action = &quot;*&quot;</code> の IAM ポリシー、<code>publicly_accessible = true</code> の RDS、<code>encrypted = false</code> の EBS といった、人間が忘れがちなパターンや見落としがちな書き間違いを、AI は平然と書き上げてしまいます。</p>
<p>以前に執筆した記事(Claude Code 経由で AWS にアクセスする際の IAM ガードレール)では、AI エージェント自体が AWS にアクセスする時の「IAM ガードレール」について書きました。今回はその近接領域で、AI が Terraform コードを書いた後に、そのコード自体をどうチェックするのかを考えてみました。人間がレビューに入る前に機械的に弾く、シフトレフトされたガードレールをどう作るかという話です。なお本記事における「シフトレフト」は、コードレビュー前の段階に機械的チェックを前倒しで仕掛けることを指します。</p>
<p>そのために、Policy as Code(以下 PaC)ツールである trivy、checkov、conftest の 3 つを、同じサンプルコードに当てて、それぞれの役割分担を見ていきます。</p>
<h2 id="2-「管理策の実装」とは、ルールをコードで強制する仕組み">2. 「管理策の実装」とは、ルールをコードで強制する仕組み</h2><p>まず、PaC というものの位置づけを確認していきます。</p>
<p>冒頭に引用した O’Reilly の「Policy as Code」では、PaC を「管理策の実装」と表現しています。組織が採用した標準(CIS Benchmark、AWS Foundational Security Best Practices、ISMS の管理策、社内ルール、など)があり、その標準を機械的に強制するのが PaC の役目だ、ということです。そしてその結果は「追跡可能・監査可能でなければならない」と続きます。</p>
<p>つまり、PaC ツールに求められているのは次の 4 つの機能と言えます。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 構成要素 / 内容"><table>
<thead>
<tr>
<th>構成要素</th>
<th>内容</th>
</tr>
</thead>
<tbody><tr>
<td>定義</td>
<td>ポリシーを Git 等で version control されたコードとして書ける</td>
</tr>
<tr>
<td>強制</td>
<td>CI&#x2F;CD で自動評価し、違反時にビルドを止められる</td>
</tr>
<tr>
<td>テスト</td>
<td>ポリシー自体に対するユニットテストが書ける</td>
</tr>
<tr>
<td>監査</td>
<td>検出結果を SARIF 等の標準フォーマットで残せる</td>
</tr>
</tbody></table></div>
<p>これら 4 つの観点で各ツールを見ていくと、それぞれの性格の違いが浮かび上がってきます。後の章で、今回検証したツールである trivy、checkov、conftest がどの観点に強くて、どの観点に弱いのかを実際に確認していきます。</p>
<h2 id="3-違反入りの-tf-ファイルで検証環境を用意する">3. 違反入りの .tf ファイルで検証環境を用意する</h2><p>今回の検証用に、意図的にセキュリティ違反を含めた <code>.tf</code> ファイル一式と、conftest 用の自作 Rego ポリシーを用意しました。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-197i8qf-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">.</span><br><span class="line">├── insecure/                       <span class="comment"># 検証対象の Terraform コード</span></span><br><span class="line">│   ├── provider.tf                 <span class="comment"># apply 不可の mock 認証情報</span></span><br><span class="line">│   ├── s3.tf                       <span class="comment"># CIS 2.1.1, 2.1.3, 2.1.5, 3.6</span></span><br><span class="line">│   ├── iam.tf                      <span class="comment"># CIS 1.15, 1.16, 1.22</span></span><br><span class="line">│   ├── network.tf                  <span class="comment"># CIS 3.9, 5.2, 5.3</span></span><br><span class="line">│   ├── logging.tf                  <span class="comment"># CIS 3.1, 3.2, 3.7</span></span><br><span class="line">│   ├── ebs.tf                      <span class="comment"># CIS 2.2.1</span></span><br><span class="line">│   ├── rds.tf                      <span class="comment"># FSBP RDS.2, 3, 8, 11</span></span><br><span class="line">│   └── kms.tf                      <span class="comment"># CIS 3.8</span></span><br><span class="line">└── policies/                       <span class="comment"># conftest 用 自作 Rego ポリシー</span></span><br><span class="line">    ├── s3.rego                     <span class="comment"># CIS 2.1.5</span></span><br><span class="line">    ├── iam.rego                    <span class="comment"># CIS 1.16, 1.22 関連</span></span><br><span class="line">    ├── network.rego                <span class="comment"># CIS 5.2, 5.3</span></span><br><span class="line">    └── rds.rego                    <span class="comment"># FSBP RDS.2, 3, 8, 11</span></span><br></pre></td></tr></table></figure></div>

<h3 id="insecure-検証対象の-Terraform-コード">insecure&#x2F;(検証対象の Terraform コード)</h3><details>
<summary>insecure/provider.tf</summary>

<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-197i8qf-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-2" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">terraform</span> {
  required_version = <span class="string">&quot;&gt;= 1.5&quot;</span>
  required_providers {
    aws = {
      source  = <span class="string">&quot;hashicorp/aws&quot;</span>
      version = <span class="string">&quot;~&gt; 6.0&quot;</span>
    }
  }
}
<span class="line"></span>
<span class="keyword">provider</span> <span class="string">&quot;aws&quot;</span> {
  region                      = <span class="string">&quot;ap-northeast-1&quot;</span>
  access_key                  = <span class="string">&quot;mock_access_key&quot;</span>
  secret_key                  = <span class="string">&quot;mock_secret_key&quot;</span>
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true
}</pre></td></tr></tbody></table></figure></div>

</details>

<details>
<summary>insecure/s3.tf</summary>

<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-197i8qf-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-3" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># VIOLATION: S3 bucket without public access block</span>
<span class="comment"># CIS AWS Foundations: 2.1.5 (S3 Block Public Access)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;public_data&quot;</span> {
  bucket = <span class="string">&quot;insecure-public-data-bucket-example&quot;</span>
}
<span class="line"></span>
<span class="comment"># VIOLATION: S3 bucket with public-read ACL</span>
<span class="comment"># CIS AWS Foundations: 2.1.5</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket_acl&quot;</span> <span class="string">&quot;public_data_acl&quot;</span> {
  bucket = aws_s3_bucket.public_data.id
  acl    = <span class="string">&quot;public-read&quot;</span>
}
<span class="line"></span>
<span class="comment"># VIOLATION: S3 bucket without server-side encryption</span>
<span class="comment"># CIS AWS Foundations: 2.1.1 (Ensure S3 buckets encrypted at rest)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;unencrypted&quot;</span> {
  bucket = <span class="string">&quot;insecure-unencrypted-bucket-example&quot;</span>
}
<span class="line"></span>
<span class="comment"># VIOLATION: S3 bucket without versioning and access logging</span>
<span class="comment"># CIS AWS Foundations: 2.1.3 (versioning), 3.6 (S3 access logging)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;no_versioning_logging&quot;</span> {
  bucket = <span class="string">&quot;insecure-no-versioning-bucket-example&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

</details>

<details>
<summary>insecure/iam.tf</summary>

<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-197i8qf-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-4" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># VIOLATION: IAM policy with wildcard action and resource (full admin)</span>
<span class="comment"># CIS AWS Foundations: 1.16 (Ensure IAM policies that allow full &quot;*:*&quot; privileges are not attached)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_iam_policy&quot;</span> <span class="string">&quot;admin_wildcard&quot;</span> {
  name = <span class="string">&quot;insecure-admin-wildcard-policy&quot;</span>
  policy = jsonencode({
    Version = <span class="string">&quot;2012-10-17&quot;</span>
    Statement = [
      {
        Effect   = <span class="string">&quot;Allow&quot;</span>
        Action   = <span class="string">&quot;*&quot;</span>
        Resource = <span class="string">&quot;*&quot;</span>
      }
    ]
  })
}
<span class="line"></span>
<span class="comment"># VIOLATION: IAM role assumable by any AWS account</span>
<span class="comment"># CIS AWS Foundations: related to 1.22 (cross-account trust restrictions)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_iam_role&quot;</span> <span class="string">&quot;open_assume_role&quot;</span> {
  name = <span class="string">&quot;insecure-open-assume-role&quot;</span>
  assume_role_policy = jsonencode({
    Version = <span class="string">&quot;2012-10-17&quot;</span>
    Statement = [
      {
        Effect = <span class="string">&quot;Allow&quot;</span>
        Principal = {
          AWS = <span class="string">&quot;*&quot;</span>
        }
        Action = <span class="string">&quot;sts:AssumeRole&quot;</span>
      }
    ]
  })
}
<span class="line"></span>
<span class="comment"># VIOLATION: IAM user with directly attached policy (should use group)</span>
<span class="comment"># CIS AWS Foundations: 1.15 (Ensure IAM Users Receive Permissions Only Through Groups)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_iam_user&quot;</span> <span class="string">&quot;direct_attach_user&quot;</span> {
  name = <span class="string">&quot;insecure-direct-attach-user&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_iam_user_policy_attachment&quot;</span> <span class="string">&quot;direct_attach&quot;</span> {
  user       = aws_iam_user.direct_attach_user.name
  policy_arn = aws_iam_policy.admin_wildcard.arn
}</pre></td></tr></tbody></table></figure></div>

</details>

<details>
<summary>insecure/network.tf</summary>

<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-197i8qf-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-5" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># VPC without flow logs</span>
<span class="comment"># CIS AWS Foundations: 3.9 (Ensure VPC flow logging is enabled in all VPCs)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_vpc&quot;</span> <span class="string">&quot;insecure_vpc&quot;</span> {
  cidr_block = <span class="string">&quot;10.0.0.0/16&quot;</span>
  <span class="comment"># VIOLATION: no associated aws_flow_log resource</span>
}
<span class="line"></span>
<span class="comment"># VIOLATION: Security group allows SSH from 0.0.0.0/0</span>
<span class="comment"># CIS AWS Foundations: 5.2 (Ensure no SG allows ingress from 0.0.0.0/0 to port 22)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_security_group&quot;</span> <span class="string">&quot;ssh_open&quot;</span> {
  name        = <span class="string">&quot;insecure-ssh-open&quot;</span>
  description = <span class="string">&quot;Allow SSH from anywhere&quot;</span>
  vpc_id      = aws_vpc.insecure_vpc.id
<span class="line"></span>
  ingress {
    from_port   = <span class="number">22</span>
    to_port     = <span class="number">22</span>
    protocol    = <span class="string">&quot;tcp&quot;</span>
    cidr_blocks = [<span class="string">&quot;0.0.0.0/0&quot;</span>]
  }
}
<span class="line"></span>
<span class="comment"># VIOLATION: Security group allows RDP from 0.0.0.0/0</span>
<span class="comment"># CIS AWS Foundations: 5.3 (Ensure no SG allows ingress from 0.0.0.0/0 to port 3389)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_security_group&quot;</span> <span class="string">&quot;rdp_open&quot;</span> {
  name        = <span class="string">&quot;insecure-rdp-open&quot;</span>
  description = <span class="string">&quot;Allow RDP from anywhere&quot;</span>
  vpc_id      = aws_vpc.insecure_vpc.id
<span class="line"></span>
  ingress {
    from_port   = <span class="number">3389</span>
    to_port     = <span class="number">3389</span>
    protocol    = <span class="string">&quot;tcp&quot;</span>
    cidr_blocks = [<span class="string">&quot;0.0.0.0/0&quot;</span>]
  }
}
<span class="line"></span>
<span class="comment"># VIOLATION: Security group allows all traffic from 0.0.0.0/0</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_security_group&quot;</span> <span class="string">&quot;all_open&quot;</span> {
  name        = <span class="string">&quot;insecure-all-open&quot;</span>
  description = <span class="string">&quot;Allow all traffic&quot;</span>
  vpc_id      = aws_vpc.insecure_vpc.id
<span class="line"></span>
  ingress {
    from_port   = <span class="number">0</span>
    to_port     = <span class="number">0</span>
    protocol    = <span class="string">&quot;-1&quot;</span>
    cidr_blocks = [<span class="string">&quot;0.0.0.0/0&quot;</span>]
  }
}</pre></td></tr></tbody></table></figure></div>

</details>

<details>
<summary>insecure/logging.tf</summary>

<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-197i8qf-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-6" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;trail_bucket&quot;</span> {
  bucket = <span class="string">&quot;insecure-trail-bucket-example&quot;</span>
}
<span class="line"></span>
<span class="comment"># VIOLATION: CloudTrail with logging disabled</span>
<span class="comment"># CIS AWS Foundations: 3.1 (Ensure CloudTrail is enabled in all regions)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_cloudtrail&quot;</span> <span class="string">&quot;disabled&quot;</span> {
  name           = <span class="string">&quot;insecure-disabled-trail&quot;</span>
  s3_bucket_name = aws_s3_bucket.trail_bucket.id
<span class="line"></span>
  enable_logging                = false
  is_multi_region_trail         = false
  enable_log_file_validation    = false
  include_global_service_events = false
}
<span class="line"></span>
<span class="comment"># VIOLATION: CloudTrail without KMS encryption and log file validation</span>
<span class="comment"># CIS AWS Foundations: 3.2 (log file validation), 3.7 (KMS encryption)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_cloudtrail&quot;</span> <span class="string">&quot;no_encryption&quot;</span> {
  name           = <span class="string">&quot;insecure-no-encryption-trail&quot;</span>
  s3_bucket_name = aws_s3_bucket.trail_bucket.id
<span class="line"></span>
  enable_logging             = true
  enable_log_file_validation = false
  <span class="comment"># kms_key_id intentionally not set</span>
}</pre></td></tr></tbody></table></figure></div>

</details>

<details>
<summary>insecure/ebs.tf</summary>

<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-197i8qf-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-7" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># VIOLATION: EBS volume without encryption</span>
<span class="comment"># CIS AWS Foundations: 2.2.1 (Ensure EBS volume encryption is enabled)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_ebs_volume&quot;</span> <span class="string">&quot;unencrypted&quot;</span> {
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  size              = <span class="number">10</span>
  encrypted         = false
}
<span class="line"></span>
<span class="comment"># VIOLATION: EBS snapshot from unencrypted volume</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_ebs_snapshot&quot;</span> <span class="string">&quot;unencrypted_snapshot&quot;</span> {
  volume_id = aws_ebs_volume.unencrypted.id
}</pre></td></tr></tbody></table></figure></div>

</details>

<details>
<summary>insecure/rds.tf</summary>

<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-197i8qf-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-8" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public_a&quot;</span> {
  vpc_id            = aws_vpc.insecure_vpc.id
  cidr_block        = <span class="string">&quot;10.0.1.0/24&quot;</span>
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public_b&quot;</span> {
  vpc_id            = aws_vpc.insecure_vpc.id
  cidr_block        = <span class="string">&quot;10.0.2.0/24&quot;</span>
  availability_zone = <span class="string">&quot;ap-northeast-1b&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_db_subnet_group&quot;</span> <span class="string">&quot;insecure&quot;</span> {
  name       = <span class="string">&quot;insecure-rds-subnet-group&quot;</span>
  subnet_ids = [aws_subnet.public_a.id, aws_subnet.public_b.id]
}
<span class="line"></span>
<span class="comment"># VIOLATION: RDS publicly accessible, unencrypted, no backup, hardcoded password</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_db_instance&quot;</span> <span class="string">&quot;insecure_db&quot;</span> {
  identifier           = <span class="string">&quot;insecure-public-db&quot;</span>
  engine               = <span class="string">&quot;mysql&quot;</span>
  engine_version       = <span class="string">&quot;8.0&quot;</span>
  instance_class       = <span class="string">&quot;db.t3.micro&quot;</span>
  allocated_storage    = <span class="number">20</span>
  username             = <span class="string">&quot;admin&quot;</span>
  password             = <span class="string">&quot;InsecurePassword123!&quot;</span>
  db_subnet_group_name = aws_db_subnet_group.insecure.name
<span class="line"></span>
  publicly_accessible     = true
  storage_encrypted       = false
  backup_retention_period = <span class="number">0</span>
  deletion_protection     = false
  skip_final_snapshot     = true
}</pre></td></tr></tbody></table></figure></div>

</details>

<details>
<summary>insecure/kms.tf</summary>

<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-197i8qf-9" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-9" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># VIOLATION: KMS key without rotation enabled</span>
<span class="comment"># CIS AWS Foundations: 3.8 (Ensure rotation for customer-created CMKs is enabled)</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_kms_key&quot;</span> <span class="string">&quot;no_rotation&quot;</span> {
  description             = <span class="string">&quot;insecure-no-rotation-key&quot;</span>
  deletion_window_in_days = <span class="number">7</span>
  enable_key_rotation     = false
}</pre></td></tr></tbody></table></figure></div>

</details>

<h3 id="policies-conftest-用-Rego-ポリシー">policies&#x2F;(conftest 用 Rego ポリシー)</h3><details>
<summary>policies/s3.rego</summary>

<div class="code-block"><figure class="highlight rego"><input type="checkbox" id="code-wrap-197i8qf-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> rego.v1</span><br><span class="line"></span><br><span class="line"><span class="comment"># CIS 2.1.5: S3 buckets should not allow public read access via ACL</span></span><br><span class="line"><span class="comment"># 注意: Conftest の hcl2 パーサはリソースを配列でラップするため、</span></span><br><span class="line"><span class="comment"># input.resource.aws_s3_bucket_acl[name] は配列となる。要素は [_] で取り出す。</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    resource <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_s3_bucket_acl[name][_]</span><br><span class="line">    resource.acl <span class="operator">==</span> <span class="string">&quot;public-read&quot;</span></span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;S3 bucket ACL &#x27;%s&#x27; grants public-read access (CIS 2.1.5)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    resource <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_s3_bucket_acl[name][_]</span><br><span class="line">    resource.acl <span class="operator">==</span> <span class="string">&quot;public-read-write&quot;</span></span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;S3 bucket ACL &#x27;%s&#x27; grants public-read-write access (CIS 2.1.5)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

</details>

<details>
<summary>policies/iam.rego</summary>

<div class="code-block"><figure class="highlight rego"><input type="checkbox" id="code-wrap-197i8qf-11" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-11" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> rego.v1</span><br><span class="line"></span><br><span class="line"><span class="comment"># ⚠️ Source mode の制約:</span></span><br><span class="line"><span class="comment"># Terraform HCL の jsonencode() は Conftest の hcl2 パーサで評価されず、</span></span><br><span class="line"><span class="comment"># &quot;$&#123;jsonencode(&#123;...&#125;)&#125;&quot; という文字列としてそのまま保存される。</span></span><br><span class="line"><span class="comment"># よって json.unmarshal() で構造化パースは不可。</span></span><br><span class="line"><span class="comment"># 構造化検出が必要な場合は Plan mode(terraform plan -json)を使うこと。</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># 以下のルールは Source mode 向けの「文字列マッチによる暫定検出」で、</span></span><br><span class="line"><span class="comment"># 空白やコメントに敏感な脆い実装である点に留意。</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># CIS 1.16: IAM policies should not allow full &quot;*:*&quot; privileges</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    resource <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_iam_policy[name][_]</span><br><span class="line">    <span class="built_in">contains</span>(resource.policy, <span class="string">&quot;Action&quot;</span>)</span><br><span class="line">    <span class="built_in">contains</span>(resource.policy, <span class="string">&quot;\&quot;*\&quot;&quot;</span>)</span><br><span class="line">    <span class="built_in">contains</span>(resource.policy, <span class="string">&quot;Resource&quot;</span>)</span><br><span class="line">    <span class="comment"># Action と Resource の両方が &quot;*&quot; になっているパターンをラフに検出</span></span><br><span class="line">    <span class="built_in">regex.match</span>(<span class="string">`Action\s*=\s*&quot;\*&quot;`</span>, resource.policy)</span><br><span class="line">    <span class="built_in">regex.match</span>(<span class="string">`Resource\s*=\s*&quot;\*&quot;`</span>, resource.policy)</span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;IAM policy &#x27;%s&#x27; allows full administrative privileges (CIS 1.16) [Source mode: regex match]&quot;</span>, [name])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># IAM role should not allow assume role from any AWS principal</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    resource <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_iam_role[name][_]</span><br><span class="line">    <span class="comment"># Principal.AWS = &quot;*&quot; のパターンをラフに検出</span></span><br><span class="line">    <span class="built_in">regex.match</span>(<span class="string">`AWS\s*=\s*&quot;\*&quot;`</span>, resource.assume_role_policy)</span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;IAM role &#x27;%s&#x27; can be assumed by any AWS principal [Source mode: regex match]&quot;</span>, [name])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

</details>

<details>
<summary>policies/network.rego</summary>

<div class="code-block"><figure class="highlight rego"><input type="checkbox" id="code-wrap-197i8qf-12" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-12" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> rego.v1</span><br><span class="line"></span><br><span class="line"><span class="comment"># ヘルパー: ingress ルールが指定ポートを 0.0.0.0/0 に公開しているか判定する。</span></span><br><span class="line"><span class="comment"># 同名ルールの複数定義は OR 結合になる。</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ケース 1: 通常のポート範囲指定</span></span><br><span class="line"><span class="built_in">port_exposed_to_world</span>(ingress, port) <span class="keyword">if</span> &#123;</span><br><span class="line">    ingress.cidr_blocks[_] <span class="operator">==</span> <span class="string">&quot;0.0.0.0/0&quot;</span></span><br><span class="line">    ingress.from_port <span class="operator">&lt;=</span> port</span><br><span class="line">    ingress.to_port <span class="operator">&gt;=</span> port</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># ケース 2: protocol=&quot;-1&quot; は AWS 慣習で「全プロトコル・全ポート」を意味する。</span></span><br><span class="line"><span class="comment"># from_port=0, to_port=0 の特殊値とセットで使われる。</span></span><br><span class="line"><span class="built_in">port_exposed_to_world</span>(ingress, _port) <span class="keyword">if</span> &#123;</span><br><span class="line">    ingress.cidr_blocks[_] <span class="operator">==</span> <span class="string">&quot;0.0.0.0/0&quot;</span></span><br><span class="line">    ingress.protocol <span class="operator">==</span> <span class="string">&quot;-1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># CIS 5.2: No security group should allow ingress from 0.0.0.0/0 to port 22</span></span><br><span class="line"><span class="comment"># ISMS A.8.20: ネットワークセキュリティ</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    sg <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_security_group[name][_]</span><br><span class="line">    ingress <span class="operator">:=</span> sg.ingress[_]</span><br><span class="line">    <span class="built_in">port_exposed_to_world</span>(ingress, <span class="number">22</span>)</span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;Security group &#x27;%s&#x27; allows SSH from 0.0.0.0/0 (CIS 5.2, ISMS A.8.20)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># CIS 5.3: No security group should allow ingress from 0.0.0.0/0 to port 3389</span></span><br><span class="line"><span class="comment"># ISMS A.8.20: ネットワークセキュリティ</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    sg <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_security_group[name][_]</span><br><span class="line">    ingress <span class="operator">:=</span> sg.ingress[_]</span><br><span class="line">    <span class="built_in">port_exposed_to_world</span>(ingress, <span class="number">3389</span>)</span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;Security group &#x27;%s&#x27; allows RDP from 0.0.0.0/0 (CIS 5.3, ISMS A.8.20)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

</details>

<details>
<summary>policies/rds.rego</summary>

<div class="code-block"><figure class="highlight rego"><input type="checkbox" id="code-wrap-197i8qf-13" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-13" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> rego.v1</span><br><span class="line"></span><br><span class="line"><span class="comment"># AWS FSBP [RDS.2]: RDS instances should not be publicly accessible</span></span><br><span class="line"><span class="comment"># ISMS A.8.22: ネットワークセキュリティ — 公開ネットワークと内部ネットワークの分離</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    db <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_db_instance[name][_]</span><br><span class="line">    db.publicly_accessible <span class="operator">==</span> <span class="literal">true</span></span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;RDS instance &#x27;%s&#x27; is publicly accessible (FSBP RDS.2, ISMS A.8.22)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># AWS FSBP [RDS.3]: RDS storage should be encrypted at rest</span></span><br><span class="line"><span class="comment"># ISMS A.8.24: 暗号化の利用</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    db <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_db_instance[name][_]</span><br><span class="line">    db.storage_encrypted <span class="operator">==</span> <span class="literal">false</span></span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;RDS instance &#x27;%s&#x27; has unencrypted storage (FSBP RDS.3, ISMS A.8.24)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># AWS FSBP [RDS.11]: RDS should have automatic backups enabled</span></span><br><span class="line"><span class="comment"># ISMS A.8.13: 情報のバックアップ</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    db <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_db_instance[name][_]</span><br><span class="line">    db.backup_retention_period <span class="operator">==</span> <span class="number">0</span></span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;RDS instance &#x27;%s&#x27; has no backup retention (FSBP RDS.11, ISMS A.8.13)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># AWS FSBP [RDS.8]: RDS instances should have deletion protection enabled</span></span><br><span class="line"><span class="comment"># ISMS A.8.14: 情報処理施設の冗長性 — 主要システムの偶発的損失を防ぐ</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    db <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_db_instance[name][_]</span><br><span class="line">    db.deletion_protection <span class="operator">==</span> <span class="literal">false</span></span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;RDS instance &#x27;%s&#x27; has deletion protection disabled (FSBP RDS.8, ISMS A.8.14)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># ISMS A.5.17: 認証情報 — 認証情報のハードコーディング禁止</span></span><br><span class="line"><span class="comment"># 注意: HCL の interpolation ($&#123;var.foo&#125;) で始まらない文字列リテラルを「ハードコード」とみなす。</span></span><br><span class="line"><span class="comment"># 完全な静的解析ではないため、Base64 化された値などはすり抜ける可能性あり。</span></span><br><span class="line"><span class="title">deny</span> <span class="keyword">contains</span> msg <span class="keyword">if</span> &#123;</span><br><span class="line">    db <span class="operator">:=</span> <span class="variable">input</span>.resource.aws_db_instance[name][_]</span><br><span class="line">    db.password</span><br><span class="line">    <span class="keyword">not</span> <span class="built_in">startswith</span>(db.password, <span class="string">&quot;$&#123;&quot;</span>)</span><br><span class="line">    msg <span class="operator">:=</span> <span class="built_in">sprintf</span>(<span class="string">&quot;RDS instance &#x27;%s&#x27; has hardcoded password literal (ISMS A.5.17)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

</details>

<p><code>provider.tf</code> には実在しない mock 認証情報を直接書いているので、<code>terraform apply</code> を試みても AWS API の認証で必ず失敗します。間違って実環境に流れ込む事故を防ぐための仕掛けです。</p>
<p><code>insecure/</code> 配下の <code>.tf</code> ファイルには、CIS Benchmark および AWS Foundational Security Best Practices(FSBP)に紐付く違反を合計 23 件、意図して仕込んでいます。S3 バケットを暗号化なしで作る、IAM ポリシーで <code>Action: &quot;*&quot;</code> を許可する、SSH (22) を <code>0.0.0.0/0</code> に開放する、といった「やってはいけない代表例」を網羅しました。</p>
<p><code>policies/</code> 配下の 4 ファイルは conftest の自作ポリシーで、第 6 章で詳しく取り上げます。</p>
<h2 id="4-3-つの-PaC-実現ツールの違い">4. 3 つの PaC 実現ツールの違い</h2><p>trivy、checkov、conftest を、まず基本情報で並べてみます。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 観点 / trivy / checkov / conftest"><table>
<thead>
<tr>
<th>観点</th>
<th>trivy</th>
<th>checkov</th>
<th>conftest</th>
</tr>
</thead>
<tbody><tr>
<td>提供元</td>
<td>Aqua Security</td>
<td>Bridgecrew &#x2F; Prisma Cloud</td>
<td>OPA(CNCF)</td>
</tr>
<tr>
<td>組み込みルール</td>
<td>数百〜数千</td>
<td>1,000+</td>
<td>0(全部自作)</td>
</tr>
<tr>
<td>デフォルト出力</td>
<td>違反のみ</td>
<td>passed &#x2F; failed をリソース単位で列挙</td>
<td>failed メッセージのみ列挙(サマリに passed 件数)</td>
</tr>
<tr>
<td>メンテナンスの責任</td>
<td>コミュニティ</td>
<td>コミュニティ</td>
<td>自社</td>
</tr>
</tbody></table></div>
<p>第 2 章で挙げた PaC の 4 観点(定義 &#x2F; 強制 &#x2F; テスト &#x2F; 監査)で並べると、仕様の違いがさらにはっきりします。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 観点 / trivy / checkov / conftest"><table>
<thead>
<tr>
<th>観点</th>
<th>trivy</th>
<th>checkov</th>
<th>conftest</th>
</tr>
</thead>
<tbody><tr>
<td>定義</td>
<td>組み込みルール中心。カスタムは Rego で追記可</td>
<td>組み込みルール中心。カスタムは Python &#x2F; YAML で追記可</td>
<td>すべて自作(Rego)</td>
</tr>
<tr>
<td>強制</td>
<td>CLI 終了コードで CI からビルドを止められる</td>
<td>CLI 終了コードで CI からビルドを止められる</td>
<td>CLI 終了コードで CI からビルドを止められる</td>
</tr>
<tr>
<td>テスト</td>
<td>組み込みのユニットテスト機構はなし</td>
<td>組み込みのユニットテスト機構はなし</td>
<td><code>conftest verify</code> で Rego のユニットテストが書ける</td>
</tr>
<tr>
<td>監査</td>
<td>SARIF 出力可。メッセージはルール ID 中心</td>
<td>SARIF 出力可。passed も結果に含まれる</td>
<td>メッセージを自由記述でき、管理策番号を埋め込める</td>
</tr>
</tbody></table></div>
<p>trivy はバイナリ1つで動く軽量さが特徴で、組み込みルールで網羅的に検出してくれます。checkov は Python ベースで起動は重めですが、リソース間の関係性を見たグラフチェックなど、検出の深さで光ります。</p>
<p>conftest は他の 2 つとは設計が大きく異なります。利用者がルールを記述しない限り、何も検出されません。空のディレクトリを指定して動かしてもエラーは出ませんが、違反も出てきません。これが conftest の仕様で、組み込みルールを持たない代わりに、組織固有のチェックをそのまま実装できる柔軟さがあります。</p>
<p>conftest が内部で使っている OPA(Open Policy Agent)は CNCF の Graduated プロジェクトで、Kubernetes Admission Controller など IaC 以外でも広く使われています。以前であれば Rego という独自言語を覚える必要があり、学習コストの高さが採用の壁でした。現在は生成 AI を活用して Rego を記述できるため、その負担は大きく軽減されています。</p>
<h2 id="5-同一サンプルへの、3-ツールの検出結果">5. 同一サンプルへの、3 ツールの検出結果</h2><p>以降では、これらの違いが実際の検出結果としてどう現れるかを見ていきます。</p>
<p>ここからは実際の検出結果です。検証に使ったツールのバージョンは以下の通りです。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 ツール / バージョン"><table>
<thead>
<tr>
<th>ツール</th>
<th>バージョン</th>
</tr>
</thead>
<tbody><tr>
<td>trivy</td>
<td>0.70.0</td>
</tr>
<tr>
<td>checkov</td>
<td>3.2.520</td>
</tr>
<tr>
<td>conftest</td>
<td>0.68.2(OPA 1.15.2 同梱)</td>
</tr>
</tbody></table></div>
<p>それぞれを実行したコマンドは、以下のようなシンプルなものです。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-197i8qf-14" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-14" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># trivy</span></span><br><span class="line">trivy config insecure/</span><br><span class="line"></span><br><span class="line"><span class="comment"># checkov</span></span><br><span class="line">checkov -d insecure/ --skip-download --soft-fail</span><br><span class="line"></span><br><span class="line"><span class="comment"># conftest</span></span><br><span class="line">conftest <span class="built_in">test</span> --parser hcl2 --policy policies/ insecure/*.tf</span><br></pre></td></tr></table></figure></div>

<p>同じ <code>insecure/</code> を 3 ツールに渡したときの検出件数を、ファイル別に並べたのが以下の表です。</p>
<p>注意点として、「意図した違反」の 23 件は、筆者が過去の経験から「これを違反として扱う」と判断して仕込んだ数です。ツール側の検出件数とは粒度が異なり、1 つのリソースに複数の違反が含まれていれば複数ルールが発火しますし、ツールによっては 1 つの違反に対して関連する複数のチェックが同時に反応します。そのため、検出件数を単純に比較して「多い方が優秀」と読むのではなく、ファイル別の傾向、つまりどのリソース種別でどんな差が出たかに注目してください。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 ファイル / 意図した違反 / trivy / checkov / conftest"><table>
<thead>
<tr>
<th>ファイル</th>
<th>意図した違反</th>
<th>trivy</th>
<th>checkov</th>
<th>conftest</th>
</tr>
</thead>
<tbody><tr>
<td>s3.tf</td>
<td>4</td>
<td>25</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>iam.tf</td>
<td>3</td>
<td>1</td>
<td>11</td>
<td>2</td>
</tr>
<tr>
<td>network.tf</td>
<td>4</td>
<td>7</td>
<td>9</td>
<td>4</td>
</tr>
<tr>
<td>logging.tf</td>
<td>3</td>
<td>17</td>
<td>9</td>
<td>0</td>
</tr>
<tr>
<td>ebs.tf</td>
<td>2</td>
<td>2</td>
<td>2</td>
<td>0</td>
</tr>
<tr>
<td>rds.tf</td>
<td>6</td>
<td>6</td>
<td>9</td>
<td>5</td>
</tr>
<tr>
<td>kms.tf</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>合計</td>
<td>23</td>
<td>59</td>
<td>41</td>
<td>12</td>
</tr>
</tbody></table></div>
<p>注目していただきたいのは、<code>s3.tf</code> と <code>iam.tf</code> の数値です。</p>
<p><code>s3.tf</code> に対して trivy は 25 件の違反を出した一方で、checkov は 0 件でした。これは checkov の検出ロジックの特性で、AWS provider v4 以降で <code>aws_s3_bucket</code> から分離された子リソース(<code>aws_s3_bucket_versioning</code> など)に対するグラフチェックが、本検証のデフォルト設定では動作しませんでした。同じ環境構成のまま checkov だけに依存すると、暗号化やパブリック公開といった項目が結果に現れない可能性があります。</p>
<p><code>iam.tf</code> では、trivy が 1 件、checkov が 11 件と検出件数に大きな差が出ました。今回のサンプルでは IAM ポリシーを <code>jsonencode()</code> で組み立てており、その内部の <code>Action: &quot;*&quot;</code> や <code>Principal: &#123; AWS: &quot;*&quot; &#125;</code> は、trivy 側の出力には現れませんでした。CIS 1.16 や 1.22 に該当する内容が今回の結果には含まれなかった形です(<code>jsonencode()</code> の中身の扱いは書き方やバージョンに依存するため、本検証ではこのケースで差が出た、というのが実態です)。</p>
<p>違反の構成や Terraform の書き方が変われば検出結果も変わり得ますが、今回のサンプルでは単独で完結する選択肢は存在しませんでした。</p>
<p>conftest の 12 件という数字は、今回自作した 11 個の <code>deny</code> ルールが対象リソースに対して合計 12 回発火した、という意味です。SSH 用と RDP 用のルールが、全ポート開放(<code>protocol = &quot;-1&quot;</code>)の Security Group に対して両方発火しているため、ルール数を 1 つ上回っています。組み込みルールがゼロのため、書いた分だけが検出に現れるのが conftest の性質です。</p>
<p>なお、合計件数の差(59 &#x2F; 41 &#x2F; 12)はツールの優劣を示すものではなく、上述のとおり 1 違反に対する発火数の違いと、conftest においては「書いたルールしか発火しない」設計の違いを反映したものです。</p>
<h2 id="6-自作-Rego-で「うちはこれを守る」を書き出す">6. 自作 Rego で「うちはこれを守る」を書き出す</h2><p>前章で触れた通り、conftest は書いた分しか検出しないツールです。そのため 12 件という数字は、trivy や checkov の検出数とそのまま並べて多寡を比較するものではなく、自分たちで「これを守る」と書いたチェックの結果として現れた数字です。自分たちで決めたルールを、自分たちの言葉で書き出した分だけが結果に返ってくる、というのが conftest の使い方になります。</p>
<p>例えば RDS 用に書いた <code>policies/rds.rego</code> の一部を示します。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-197i8qf-15" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-15" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import rego.v1</span><br><span class="line"></span><br><span class="line"><span class="comment"># AWS FSBP [RDS.2]: RDS instances should not be publicly accessible</span></span><br><span class="line"><span class="comment"># ISMS A.8.22: ネットワークセキュリティ — 公開ネットワークと内部ネットワークの分離</span></span><br><span class="line">deny contains msg <span class="keyword">if</span> &#123;</span><br><span class="line">    db := input.resource.aws_db_instance[name][_]</span><br><span class="line">    db.publicly_accessible == <span class="literal">true</span></span><br><span class="line">    msg := sprintf(<span class="string">&quot;RDS instance &#x27;%s&#x27; is publicly accessible (FSBP RDS.2, ISMS A.8.22)&quot;</span>, [name])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>注目すべきは、<code>msg</code> の中に「FSBP RDS.2」や「ISMS A.8.22」のような管理策番号を、書き手の判断で自由に埋め込める点です。これにより、検出結果を読んだ監査人やコンプライアンス担当者は、別途マッピング表を引かなくても「この違反はどの管理策に紐付くか」を即座に把握できます。</p>
<p>実際に conftest を実行すると、メッセージはこのように出てきます。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-197i8qf-16" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-197i8qf-16" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">FAIL - insecure/rds.tf - main - RDS instance <span class="string">&#x27;insecure_db&#x27;</span></span><br><span class="line">       is publicly accessible (FSBP RDS.2, ISMS A.8.22)</span><br></pre></td></tr></table></figure></div>

<p>trivy や checkov だとルール ID(<code>AVD-AWS-0080</code> や <code>CKV_AWS_17</code>)がメッセージに乗りますが、それが何の標準のどの管理策に対応するかは、別の文書で確認する必要があります。一方の conftest なら、メッセージ自体が説明文として完結できます。</p>
<p>これは「追跡可能性・監査可能性」という冒頭引用の要件に対する、ひとつの解答になっていると思っています。</p>
<p>なお、IAM のように <code>jsonencode()</code> で組み立てられたポリシーは、Source mode の conftest からは構造化された JSON として読み取れません。今回は正規表現マッチで暫定対応しました。正確性を求めるのであれば、<code>terraform plan -json</code> の Plan mode を使う必要があります。実用上は、組み込みルールを持つ checkov に IAM を任せて、conftest は自分たちで書きたいチェックを引き受けるという分担が現実的だと感じています。</p>
<h2 id="7-「広く-×-個別」のペアを運用に組み込む">7. 「広く × 個別」のペアを運用に組み込む</h2><p>今回の検証から見えてきた運用上の提案として、trivy &#x2F; checkov と conftest を別の役割で組み合わせるパターンを置いておきます。</p>
<p>trivy と checkov は「広く拾う」役割です。組み込みルールで違反を網羅的に検出し、シフトレフトの CI ゲートとして使う前提です。両者とも SARIF(OASIS が標準化した業界標準フォーマット)で結果を出せるので、GitHub Code Scanning に投げ込めば Pull Request の該当行に違反の注釈が付きます。これにより、人間レビュアーに引き継ぐ前の機械的な事前チェックとして利用できます。ただし、完璧にチェックできるツールは存在せず、対象とする <code>.tf</code> の内容によって、適切な組み合わせは変わってきます。</p>
<p>これに対し conftest は、自分たちで「これだけは絶対に通さない」と判断したチェック項目を、Rego として明示的に記述するためのツールです。第 6 章で示したように、<code>msg</code> に管理策番号を埋め込めるので、検出結果がそのまま監査証跡として残せます。trivy &#x2F; checkov が「広く拾う」のに対し、conftest は「狭く、明示的に、誰にでも分かる言葉で守る」担当です。</p>
<p><code>.rego</code> ファイル自体が Git で管理されていれば、「いつ、誰が、どんなチェックを追加したか」もすべて履歴として残ります。チェックそのものが履歴管理される形になり、冒頭で引用した「追跡可能・監査可能」という要件にも自然と応えられます。</p>
<p>「広く拾う」trivy &#x2F; checkov と「狭く明示する」conftest という二段構えが、今回の検証から見えた運用上の落としどころです。組み込みルールのツールと自作ルールのツールを併用するパターン自体は PaC を扱う場面でしばしば語られる構成で、今回の検証でも実感としてそこに着地しました。</p>
<h2 id="8-まとめ">8. まとめ</h2><p>本記事では、AI が書いた Terraform コードに対するシフトレフト型のガードレールとして、trivy、checkov、conftest の 3 ツールを同じサンプルに当てて検証しました。</p>
<p>検証の結果、今回のサンプルでは、ツールごとに得意な領域とそうでない領域が分かれました。違反の構成や Terraform の書き方が変われば結果も変わり得るため、適切な組み合わせは状況ごとに見極める必要があります。</p>
<p>運用上の提案として、組み込みルールで広く拾う trivy &#x2F; checkov と、自分たちで「これを守る」と決めたルールを明示する conftest を組み合わせる二段構えを示しました。conftest の <code>msg</code> に管理策番号を直接埋め込めば、検出結果は追跡可能な監査証跡としても残せます。</p>
<p>本記事で示したシフトレフト型のガードレールが、Terraform 運用の中で参考になれば幸いです。</p>
<h2 id="参照">参照</h2><ul>
<li>AWS MCP Server がGAに - Claude Codeから検証: IAMガードレール設計</li>
<li>Policy as Code (O’Reilly)</li>
<li>trivy (aquasecurity&#x2F;trivy)</li>
<li>checkov (bridgecrewio&#x2F;checkov)</li>
<li>conftest (open-policy-agent&#x2F;conftest)</li>
</ul>
]]></content>
    <summary type="html">「PaC は管理策を実装するのである。通常、PaC が実装する管理は、組織が作成または採用した標準に由来する。これらの統制は、追跡可能で監査可能でなければならない。」</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Policy-as-Code" scheme="https://future-architect.github.io/tags/Policy-as-Code/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
  </entry>
  <entry>
    <title>Terraform経験者が初めてAWS CDKを利用して感じたギャップ</title>
    <link href="https://future-architect.github.io/articles/20260522a/"/>
    <id>https://future-architect.github.io/articles/20260522a/</id>
    <published>2026-05-21T15:00:00.000Z</published>
    <updated>2026-05-21T15:00:00.000Z</updated>
    <author><name>八木雅斗</name></author>
    <content type="html"><![CDATA[<p>Terraform連載2026 の4本目です。</p>
<p>TIG（Technology Innovation Group）の八木雅斗です。</p>
<p>2023年の新卒入社以来、IaCツールは一貫してTerraformを利用してきましたが、最近の業務ではAWS CDKを利用しています。</p>
<p>AWS CDKを実際に触ってみて、これまでTerraformの考え方に慣れ親しんでいたこともあり、いくつかギャップを感じる場面がありました。<br>本記事では、現場のリアルな経験をもとに、システム構築において「Terraformユーザー視点で感じたAWS CDKの思想の違い」や「設計時に考慮が必要だったポイント」について解説していきたいと思います。</p>
<div class="note-container note-info"><span class="note-icon"></span><div>

<p>利用時のシチュエーションは以下になります。</p>
<ul>
<li>チーム体制はインフラとアプリでチームが分かれている</li>
<li>AWS CDKに直接変更を加えることのあるメンバーは3~4人</li>
<li>1環境あたりのCloudFormationテンプレートの総リソース数は7000～10000程（複数のCloudFormationスタックに分割して管理）のシステムの新規構築で利用</li>
</ul>
</div></div>

<div class="note-container note-info"><span class="note-icon"></span><div>

<p>本内容は2025年11月～2026年2月頃のツールの機能に基づいています。</p>
<ul>
<li>バージョン情報<ul>
<li>AWS CDK：2.1101.0</li>
</ul>
</li>
</ul>
</div></div>

<h2 id="リソースのターゲット指定（部分適用）に関する仕様の違い">リソースのターゲット指定（部分適用）に関する仕様の違い</h2><p>CloudFormationでは、特定のリソース（例えば1つのLambda関数の設定など）だけを更新したい場合でも、AWS CDKはスタック全体を対象にデプロイを行うため、リソース単位での部分適用という概念が異なります。</p>
<p>弊社で出しているTerraformガイドラインでも記載がある通り、検証&#x2F;本番環境ではターゲット指定でリソースをデプロイすることは避けるべきです。しかし、複数人が開発環境などで試行錯誤しながら構築している場合、他のメンバーの作業に影響を与えないようにするため、一部のリソースだけに変更を加えたいことがあります。</p>
<p>Terraformであれば、手元での検証時にターゲット指定でリソースに変更を加えるといったアプローチが取りやすいです。一方で、AWS CDKを利用しており、かつ同じスタックで管理されているリソースに変更を加えたい場合、メンバー間で同じブランチを利用するなど、チーム内でのコンフリクトを避ける運用上の工夫が必要になります。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-1yugied-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1yugied-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># AWS CDKでは、複数のリソースをまとめたスタック単位までの粒度でデプロイを行う</span></span><br><span class="line">cdk deploy Sample</span><br><span class="line"><span class="comment"># Terraformでは、リソース単位でデプロイを絞ることが可能</span></span><br><span class="line">terraform apply -target=aws_s3_bucket.sample</span><br></pre></td></tr></table></figure></div>

<h2 id="言語の自由度が高いゆえの可読性・設計の考慮点">言語の自由度が高いゆえの可読性・設計の考慮点</h2><p>インフラのリソース定義としては、コードを見るだけでどんなリソースが生成されるのかがわかりやすい「宣言的」なアプローチに慣れていると、CDKの柔軟性に少し戸惑うことがありました。</p>
<p>AWS CDKのメリットの1つとして、慣れ親しんだプログラミング言語を使用できることが挙げられます。しかし、プログラミング言語のフル機能（ループ、条件分岐、継承など）が使える強力さゆえに、それらを多用しすぎると「最終的にどんなインフラリソースがデプロイされるのか」がコードから直感的に読み取りにくくなるケースがあります。</p>
<p>この可読性の低下は、パラメータ値の確認コストやコードレビュー負荷の増加に繋がる可能性があるため注意が必要です。<br>AWS CDKを利用する際は、特有の抽象化概念だけでなく、CloudFormationの挙動や制約を理解しつつ、オブジェクト指向のインフラコードをどう設計し保守するかのルール決めが重要になります。</p>
<p>Terraformが利用するHCL（HashiCorp Configuration Language）は設定言語としての制約があるため、冗長ではあるものの、誰が書いても同じような見通しの良い「宣言的」なコードになりやすいという特徴があります。どちらが良いというよりも、チームのスキルセットや運用方針に合わせた設計ガイドラインを設けることが大切だと感じました。</p>
<h2 id="ライフサイクル分離（ignore-changes）の代替アプローチ">ライフサイクル分離（ignore_changes）の代替アプローチ</h2><p>デプロイサイクルの異なるインフラ（VPCやECSクラスタ、ECSサービス本体など）とアプリケーション（ECSタスク定義の更新とコンテナイメージのデプロイ）は、ライフサイクルを分離して管理したいケースがよくあります。</p>
<p>AWS CDKはCloudFormationをベースとしているため、リソースを完全に切り離して管理する場合には少し工夫がいります。</p>
<p>例えば、GitHub Actionsなどによって新しいタスク定義（v2、v3…）がデプロイされている稼働中の環境に対し、インフラの設定変更のために cdk deploy を実行すると、AWS CDKはコード上で定義されていた「古いタスク定義（v1）」でECSサービスを上書きしてしまう動作になります。</p>
<p>これを回避してライフサイクルを分離するためには、インフラ側でダミーのタスク定義を用意したり、AWS SDKを呼び出して現在の最新タスク定義をSSMパラメータストアから動的に取得するなど、実装上の工夫が求められます。</p>
<div class="code-block"><figure class="highlight ts"><input type="checkbox" id="code-wrap-1yugied-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1yugied-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// AWS CDKでアプリ（タスク定義やコンテナイメージ）とインフラのデプロイを分離する場合のコード例</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> cdk <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> ecs <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-ecs&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> ssm <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-ssm&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Construct</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;constructs&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">EcsServiceStack</span> <span class="keyword">extends</span> <span class="title class_ inherited__">cdk.Stack</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="attr">scope</span>: <span class="title class_">Construct</span>, <span class="attr">id</span>: <span class="built_in">string</span>, <span class="attr">props</span>?: cdk.<span class="title class_">StackProps</span></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id, props);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> cluster = <span class="keyword">new</span> ecs.<span class="title class_">Cluster</span>(<span class="variable language_">this</span>, <span class="string">&#x27;MyCluster&#x27;</span>, &#123; <span class="comment">/* 省略 */</span> &#125;);</span><br><span class="line">    <span class="comment">// GitHub Actions等が最新のタスク定義ARNを書き込むSSMパラメータ名</span></span><br><span class="line">    <span class="keyword">const</span> parameterName = <span class="string">&#x27;/app/my-service/latest-task-definition-arn&#x27;</span>;</span><br><span class="line">    <span class="comment">// cdk synth実行時にAWS環境へ直接アクセスし、現在の値を取得</span></span><br><span class="line">    <span class="comment">// パラメータが存在しない(初回デプロイ)時はエラーにならず、&#x27;dummy-value-...&#x27;という文字列を返す</span></span><br><span class="line">    <span class="keyword">const</span> latestTaskDefArn = ssm.<span class="property">StringParameter</span>.<span class="title function_">valueFromLookup</span>(<span class="variable language_">this</span>, parameterName);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="attr">taskDefinition</span>: ecs.<span class="property">ITaskDefinition</span>;</span><br><span class="line">    <span class="comment">// CDKが返した文字列に&#x27;dummy&#x27;が含まれているかで、初回デプロイか判定</span></span><br><span class="line">    <span class="keyword">if</span> (latestTaskDefArn.<span class="title function_">includes</span>(<span class="string">&#x27;dummy&#x27;</span>)) &#123;</span><br><span class="line">      <span class="comment">// 【初回デプロイ時】 ダミーのタスク定義を作成</span></span><br><span class="line">      <span class="keyword">const</span> dummyTaskDef = <span class="keyword">new</span> ecs.<span class="title class_">FargateTaskDefinition</span>(<span class="variable language_">this</span>, <span class="string">&#x27;DummyTaskDef&#x27;</span>, &#123;</span><br><span class="line">        <span class="attr">memoryLimitMiB</span>: <span class="number">512</span>,</span><br><span class="line">        <span class="attr">cpu</span>: <span class="number">256</span>,</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="comment">// サービスを起動させるためだけの適当な軽量コンテナ（nginxなど）を定義</span></span><br><span class="line">      dummyTaskDef.<span class="title function_">addContainer</span>(<span class="string">&#x27;DummyContainer&#x27;</span>, &#123;</span><br><span class="line">        <span class="attr">image</span>: ecs.<span class="property">ContainerImage</span>.<span class="title function_">fromRegistry</span>(<span class="string">&#x27;public.ecr.aws/nginx/nginx:latest&#x27;</span>),</span><br><span class="line">        <span class="attr">logging</span>: ecs.<span class="property">LogDrivers</span>.<span class="title function_">awsLogs</span>(&#123; <span class="attr">streamPrefix</span>: <span class="string">&#x27;dummy&#x27;</span> &#125;),</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">      taskDefinition = dummyTaskDef;</span><br><span class="line">      <span class="comment">// ※この後、アプリ側のCI/CDパイプラインを実行して正規のイメージをデプロイし、</span></span><br><span class="line">      <span class="comment">// SSMパラメータ（parameterName）を作成・更新する</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 【2回目以降】 GitHub Actions等が更新した最新のタスク定義ARNを取得</span></span><br><span class="line">      taskDefinition = ecs.<span class="property">TaskDefinition</span>.<span class="title function_">fromTaskDefinitionArn</span>(</span><br><span class="line">        <span class="variable language_">this</span>,</span><br><span class="line">        <span class="string">&#x27;ImportedTaskDef&#x27;</span>,</span><br><span class="line">        latestTaskDefArn</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ～～省略～～</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 分岐して決まったタスク定義を使ってECSサービスを構築</span></span><br><span class="line">    <span class="keyword">new</span> ecs.<span class="title class_">FargateService</span>(<span class="variable language_">this</span>, <span class="string">&#x27;MyService&#x27;</span>, &#123;</span><br><span class="line">      cluster,</span><br><span class="line">      taskDefinition,</span><br><span class="line">      <span class="attr">desiredCount</span>: <span class="number">1</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>▼少し古いですが、より詳しく知りたい方は以下の動画が参考になると思います。</p>
<p>https://youtu.be/fvZGIcf6xKU?t=1205</p>
<p>Terraformの場合は、特定のプロパティに対する外部からの変更をIaCの管理対象外とする機能が備わっています。下記のように <code>lifecycle &#123; ignore_changes = [task_definition] &#125;</code> と追記するだけで、タスク定義の変更をインフラ側から無視し、CI&#x2F;CDパイプラインから安全にデプロイし続けるといった切り分けが容易に行えます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1yugied-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1yugied-3" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># Terraformでタスク定義の変更を無視する例</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_ecs_service&quot;</span> <span class="string">&quot;app&quot;</span> {
  name            = <span class="string">&quot;my-service&quot;</span>
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  <span class="comment"># ~省略~</span>
  lifecycle {
    ignore_changes = [ <span class="comment"># インフラコード側はタスク定義の変更を完全に無視する</span>
      task_definition,
    ]
  }
}</pre></td></tr></tbody></table></figure></div>

<h2 id="差分検知（ドリフト検出）に関する仕様の違い">差分検知（ドリフト検出）に関する仕様の違い</h2><h3 id="cdk-diffにおける手動変更の扱い"><code>cdk diff</code>における手動変更の扱い</h3><p><code>cdk diff</code>では、デプロイ時にAWS環境上の「前回デプロイ時のスタックの状態」と手元で合成した「新しいCloudFormationテンプレート」を比較して差分を検知・表示します。そのため、マネジメントコンソールなどで直接リソースを手動変更してしまった場合（いわゆるドリフト）、<code>cdk diff</code> を実行してもその手動変更との差分は検知されません。</p>
<p>Terraformの場合、<code>terraform plan</code>を実行するたびに現在のAWS上の実リソース状態を読み取り（いわゆるRefresh）、コードとの差分を表示してくれます。</p>
<h3 id="ドリフト検知機能とそのカバレッジ">ドリフト検知機能とそのカバレッジ</h3><p>AWS CDKが内部で利用しているCloudFormationには、「前回デプロイ時のスタックの状態」と「AWS上の実リソース」の差分を検知する「ドリフト検出」という機能があり、CDK CLIからは<code>cdk drift</code>で実行できます。</p>
<p>https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-stack-drift.html</p>
<p>また、2025年11月からCloudFormationではテンプレートと実リソースの状態を⽐較し、ドリフト検知されたリソースをテンプレートの状態に修復できるようになっています（※CDK CLIからも<code>cdk deploy --revert-drift</code>で実行できるようです。PR#1127）</p>
<img fetchpriority="high" src="/images/2026/20260522a/image.png" alt="image.png" width="800" height="451">

<p>AWS Infrastructure as Code の新機能 2025 総まとめ 〜SA 4人による怒涛のデモ祭り〜より引用</p>
<p>https://aws.amazon.com/jp/about-aws/whats-new/2025/11/configuration-drift-enhanced-cloudformation-sets/</p>
<p>ただし、CloudFormationの「ドリフトの検知」ではサポートされていないリソースが存在し、サポート外のリソースについては、マネジメントコンソールから手動で設定変更されたり削除されたりしても、検知できません。</p>
<p>▼ドリフト検知可能なリソースは以下を参照（※記載のないAWSリソースはドリフト検知不可）</p>
<p>https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/resource-import-supported-resources.html</p>
<p>インフラの状態を厳密にIaC側と同期・監視したい場合、これらの検知対象リソースの制限についても事前に考慮しておく必要があります。</p>
<h3 id="テンプレートに未定義のプロパティにおけるドリフト検知の仕様">テンプレートに未定義のプロパティにおけるドリフト検知の仕様</h3><p>AWS CDK（CloudFormation）のドリフト検出機能は、基本的に「テンプレート内に明示的に記述されているプロパティ」を追跡の対象とする仕様になっています。<br>そのため、コード上で指定せずにAWS側のデフォルト値に任せているプロパティについては、後からマネジメントコンソール等で手動変更された場合にドリフトとして検知されないケースがあります。</p>
<p>https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-stack-drift.html#what-is-drift</p>
<p>Terraformの場合、AWSプロバイダーがAWS APIからリソースの「すべてのパラメータの状態」を取得してtfstateファイルに保持するため、明示的に記述しておらずデフォルト値が利用されていても、差分があれば検知されます。</p>
<p>もしAWS CDKを利用していて、厳密な構成管理やドリフト検知を行いたい場合は、「意図せず変更されるとシステムに影響が出る重要なプロパティについては、デフォルト値であってもあえてCDKのコード上で明示的に定義しておく」といったアプローチを取り、この仕様の違いを上手くカバーする必要があります。</p>
<p>IaC管理している場合、基本的にはリソースの手動変更は避けるべきです。しかし、試行錯誤を伴う構築時や緊急時にデプロイコストの観点から、一時的に手動変更することがあります。</p>
<p>手動変更後に設定を戻し忘れた際など、厳密な状態管理（コードと実リソースの完全同期）を求めるプロジェクトにおいては、Stateファイルによる管理モデルを持つTerraformの方が構成の乖離に気付きやすいと感じました。</p>
<h2 id="デプロイ時間の違いと開発体験">デプロイ時間の違いと開発体験</h2><p>AWS CDKにおける差分表示（<code>cdk diff</code>）やデプロイ（<code>cdk deploy</code>）では、コードのコンパイル処理、CloudFormationテンプレートの合成、そしてAWS上でのスタック作成・更新というステップを踏むアーキテクチャになっています。</p>
<p>また、スタックを個別指定して実行した場合でも、基本的にはスタックの上位概念であるAppレベル配下の全リソースを含んだテンプレート合成プロセスが走るため、リソースやスタックが増加してくると、コマンド実行から結果が返ってくるまでの待機時間が長くなります。</p>
<p>AWS CDKの大きなメリットとして、TypeScriptなどで型の恩恵を受けられることが挙げられます。しかし、実際に大規模なリソースを構築していくと、コーディング中の型補完のメリット以上に、裏側で動くCloudFormationのデプロイ待機時間の方が開発体験に影響を与えるシーンがありました。</p>
<p>Terraformの <code>terraform apply</code> では、CloudFormationを介さずAWSのAPIを直接並列で呼び出す仕組みになっています。ステータスのポーリングやパラメータの検証もAPIベースで行われるため、リソースの作成や更新のスピードが比較的速い傾向にあります。<br>Terraformにはプログラミング言語のような型補完はありませんが、IDEプラグイン（Language Server）を活用すればHCLの構文チェックやリソース名補完は一定機能するため、「変更差分の確認からデプロイ完了までのサイクル」を短く回したい場合には、APIベースのTerraformの方がテンポ良く開発を進められると感じました。</p>
<h2 id="実行時バリデーションのタイミングの違い">実行時バリデーションのタイミングの違い</h2><h3 id="セッション確認と初期化プロセス">セッション確認と初期化プロセス</h3><p>CDKでデプロイコマンド（<code>cdk deploy</code>）を実行すると、まずローカルでコードの合成処理が走り、その後にAWS環境へのアクセスが行われます。そのため、SSOのセッションが切れていた場合、ローカルでの合成処理が終わったタイミングで初めて認証エラーとして弾かれてしまうことがあります。</p>
<p><code>terraform plan</code> や <code>terraform apply</code> の場合、コマンドを実行した直後にプロバイダーの認証情報を確認し、管理対象の全リソースの最新状態をAWS API経由で取得（Refresh）しに行きます。そのため、認証切れや権限不足といったエラーがコマンド実行の初期段階で即座に検知されるという違いがあります。</p>
<h3 id="デプロイするスタックの存在確認">デプロイするスタックの存在確認</h3><p>同様に、<code>cdk diff</code> や <code>cdk deploy</code> でスタック名をタイポしてしまった場合、<code>No stacks match the name(s) xxx</code> というエラーにたどり着くまでに合成処理の時間を待つケースがあり、再試行までのサイクルに少し時間を要することがありました。</p>
<h2 id="L2コンストラクトの抽象度と現実の要件のギャップ">L2コンストラクトの抽象度と現実の要件のギャップ</h2><p>CDKのL2コンストラクトは「少ない記述量でAWSが用意したベストプラクティスの枠組みで構成を作ってくれる」という触れ込みであり、CDKのメリットの1つでもあります。しかし、実際の開発現場の要件を満たそうとすると、この抽象化の高さゆえに恩恵を受けづらくなる場面がありました。</p>
<p>その例として、ec2.Vpcがあります。これをデフォルトのまま作成すると、各アベイラビリティゾーンに時間課金が発生するNAT Gatewayが自動的に作成されます。<br>CDKでL2コンストラクトを使うなら、その中身（デフォルトで何が作られるか）を理解しておかないと意図しないリソースが出来上がるため、結局どのようなものが作成されるか裏側の仕様を確認する必要性が生じます。</p>
<p>https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/best-practices.html#best-practices-constructs</p>
<p>また、実際の開発現場の要件では「社内の共通KMSカスタマーマネージドキーで必ず暗号化する」「デフォルトで自動付与される広すぎるIAM権限を削る」といった厳密なセキュリティポリシーに従う必要があります。</p>
<p>このような細かい要件を適用しようとすると、デフォルト値の変更や「エスケープハッチ（addPropertyOverrideなど）」と呼ばれる機能を使って生成されるCloudFormationのJSONを直接上書きしたり、あるいは抽象化を諦めて「L1コンストラクト」を使用することになります。結果として、コード量が増加し、最終的に作成されるリソースやパラメータが直感的に分かりにくくなってしまうという課題がありました。</p>
<p>Terraformの場合、「APIと1対1で対応するすべてのプロパティを明示的に宣言する」アプローチをとっています。設定すべきセキュリティ要件のパラメータが増えたとしても、HCLのフラットで宣言的な構文のまま記述を積み上げる形になるため、要件が複雑化しても可読性を維持しやすいという特徴があると感じました。</p>
<h2 id="Terraform経験者がAWS-CDKを始める時のオススメ参考書">Terraform経験者がAWS CDKを始める時のオススメ参考書</h2><p>Terraformに慣れ親しんでいると、どうしてもその運用フローや思考回路のままAWS CDKを触ってしまいがちです。</p>
<p>そのため、Terraform経験者でこれからCDKを利用される際には、「［詳解］AWS Infrastructure as Code――使って比べるTerraform＆AWS CDK」 第8章「Terraform &amp; AWS CDK 注意すべき相違点」が、非常に\参考になると思います。</p>

<img src="/images/2026/20260522a/cdk.png" alt="" width="500" height="631" loading="lazy">


<p>Terraformにおける「当たり前」や「ベストプラクティス」が、AWS CDKの世界ではそのまま適用できない、あるいは異なるアプローチが必要になるケースを体系的に知ることができます。</p>
<h2 id="おわりに">おわりに</h2><p>本記事では、Terraform経験者がシステムの構築でAWS CDKを利用してみた結果、両者のアーキテクチャや思想の違いから生じるギャップや、実際の開発要件を適用する際の考慮点についてまとめました。</p>
<p>今回はTerraformと比較した際の注意点や運用上の工夫を中心に紹介しましたが、AWS CDKは「アプリケーションエンジニアが使い慣れた言語でインフラを定義できる」「ベストプラクティスが組み込まれたコンストラクトでスピーディーに立ち上げられる」といった、Terraformにはないメリットを持っています。</p>
<p>特に、AWSの知識が浅いものの、ひとまずベストプラクティスに沿って動くものを構築したい場合などでは大いに活用できるのではないかと思います。</p>
<p>本記事が、Terraform経験者がはじめてAWS CDKを利用する時のお役に立てれば幸いです🙏</p>
]]></content>
    <summary type="html">入社以来、IaCツールは一貫してTerraformを利用してきましたが、最近の業務ではAWS CDKを利用しています。AWS CDKを実際に触ってみて、これまでTerraformの考え方に慣れ親しんでいたこともあり、いくつかギャップを感じる場面がありました。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="AWS CDK" scheme="https://future-architect.github.io/tags/AWS-CDK/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="技術選定" scheme="https://future-architect.github.io/tags/%E6%8A%80%E8%A1%93%E9%81%B8%E5%AE%9A/"/>
  </entry>
  <entry>
    <title>HCP Terraform（旧Terraform Cloud）の基礎とサーバーレスアーキテクチャ選定のポイント</title>
    <link href="https://future-architect.github.io/articles/20260521a/"/>
    <id>https://future-architect.github.io/articles/20260521a/</id>
    <published>2026-05-20T15:00:00.000Z</published>
    <updated>2026-05-20T15:00:00.000Z</updated>
    <author><name>片岡久人</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260521a/top.jpg" alt="" width="800" height="436">

<p>Terraform連載2026 の3本目です。</p>
<h2 id="1-はじめに">1. はじめに</h2><p>こんにちは。製造エネルギーグループの片岡久人です。</p>
<p>インフラストラクチャをコードで管理する「Terraform」と、そのマネージドサービスである「Terraform Cloud（現：HCP Terraform）」についての入門的な話をします。</p>
<p>後半では、私が普段関わっているようなサーバーレスアーキテクチャのプロジェクトにおいて、導入時に気をつけたい「コストの落とし穴」と、それを踏まえてアーキテクチャ選定時の考え方についても深堀りしてみたいと思います。</p>
<h2 id="2-Terraformとは？">2. Terraformとは？</h2><p>まず、Terraform自体について軽くおさらいしましょう。</p>
<p>Terraformは、HashiCorp社が提供しているIaC（Infrastructure as Code）ツールです。</p>
<p>AWS、Google Cloud、Azureなどのクラウドインフラはもちろん、様々なSaaSの設定などを、HCL（HashiCorp Configuration Language）という宣言的なコードで定義・構築ができます。「画面（コンソール）をポチポチ操作してインフラを作る」のではなく、「コードを書いて実行したらインフラができる」という状態を作れるため、作業の自動化やレビューがしやすくなるのが特徴です。</p>
<h2 id="3-Terraform-Cloud（現：HCP-Terraform）とは？">3. Terraform Cloud（現：HCP Terraform）とは？</h2><p>次にマネージドサービス側のお話です。</p>
<p>名称についてですが、HashiCorp Cloud Platform（HCP）へのブランド統合の一環として、2024年4月に「Terraform Cloud（TFC）」から「<strong>HCP Terraform</strong>」へと公式に名称変更されました。（その後2025年2月にIBMによる買収が完了するなど、周辺環境にも大きな変化が起きています）</p>
<p>手元のPC単体でTerraformを実行する場合、インフラの現状を記録する「State（状態）ファイル」の管理や、複数人での同時作業による競合などが大きな課題になります。HCP Terraformは、これらの課題を解決し、チーム開発を安全かつ効率的に行うための様々な機能をSaaSとして提供してくれます。具体的には以下のような点が非常に便利です。</p>
<h3 id="Stateファイルの安全な一元管理とロック">Stateファイルの安全な一元管理とロック</h3><p>OSS版のTerraformをチームで使う場合、AWSのS3などを自前で用意してStateファイルを共有し、同時実行の競合を防ぐ『ロックの仕組み』を別途構築する必要があります。</p>
<p>HCP Terraformでは、これらが<strong>標準機能として最初から用意されている</strong>ため、バックエンドの設計に悩むことなく安全なチーム開発を始められます。</p>
<h3 id="柔軟なリモート実行と自動化">柔軟なリモート実行と自動化</h3><p>GitHubなどのバージョン管理システム（VCS）と連携することで、CI&#x2F;CDパイプラインを簡単に構築できます。</p>
<ul>
<li><strong>Pull Request作成時</strong>：自動で <code>terraform plan</code>（プレビュー）を実行</li>
<li><strong>マージ時</strong>：自動で <code>terraform apply</code>（適用）を実行</li>
</ul>
<p>CLIやAPIからの実行時もクラウド上のリモート環境で処理されるため、ローカルPCの環境依存をなくし、セキュアに一元化できます。</p>
<h3 id="動的プロバイダークレデンシャル">動的プロバイダークレデンシャル</h3><p>AWSやGoogle Cloud等とOIDC連携することで、実行（Run）のたびに<strong>一時的なクレデンシャルを自動発行</strong>して利用できます。静的なアクセスキーをHCP Terraform上に手動で保存するリスクがなくなり、セキュリティが大幅に向上します。</p>
<h3 id="ガバナンスとセキュリティ">ガバナンスとセキュリティ</h3><p>「特定のサイズのインスタンスしか作成してはいけない」といった社内ルールをコード（SentinelやOPA）で定義し、自動チェックする機能や、きめ細かいロールベースのアクセス制御（RBAC）が可能です。</p>
<blockquote>
<p><strong>※プランに関する注意点</strong><br>FreeプランではOPAを利用した小規模なポリシー適用（1ポリシーセット&#x2F;5ポリシーまで）に限定されます。HashiCorp独自のSentinelの利用、無制限のポリシー適用、高度なチーム管理機能を利用するには有料プランが必要です。</p>
</blockquote>
<h3 id="ドリフト検出">ドリフト検出</h3><p>Terraformで管理している設定と、実際のクラウドリソースの設定に差異（手動でこっそり変更されてしまった等）が発生していないかを定期的にチェックして通知してくれます。</p>
<blockquote>
<p><strong>※プランに関する注意点</strong><br>高度な自動スケジュールによるドリフト検出などは上位プラン（Standard&amp;Premium）に限定された機能となります。</p>
</blockquote>
<p>チームや組織で本格的にTerraformを運用し、インフラの品質を保つためには、強力なプラットフォームです。</p>
<p>上記で挙げた以外にも、自社専用のモジュールを共有できる「プライベートレジストリ」や、プライベートネットワーク内の隔離されたインフラを管理できる「HCP Terraformエージェント」など様々な機能がありますので、詳細は公式ドキュメントをご確認ください。</p>
<ul>
<li>HCP Terraform 公式ドキュメント</li>
<li>Terraformの次のステップ!? HCP Terraformの主要機能を紹介</li>
</ul>
<h2 id="4-アーキテクチャ選定とコストの落とし穴">4. アーキテクチャ選定とコストの落とし穴</h2><p>HCP Terraformは非常に便利なツールですが、Cloud Run Functions（旧Cloud Functions）などを多用する「サーバーレス開発」において、<strong>「なんでもかんでもTerraformで管理しようとする」と、思わぬコストの罠にハマる</strong>ことがあります。まずは具体的な料金体系とコスト感を見てみましょう。</p>
<h3 id="HCP-Terraformの料金体系と具体的なコスト感">HCP Terraformの料金体系と具体的なコスト感</h3><p>現在のHCP Terraformは、<strong>RUM（Resources Under Management：管理リソース数）</strong>ベースの課金体系を採用しています。つまり、Terraform（Stateファイル）で管理しているリソースの数に比例して月額コストが発生します。</p>
<p>現在は下記のようなプランになっています。<br>500リソースを超えた場合の各プランの単価（1リソースあたり&#x2F;月）は以下の通りです。</p>
<ul>
<li><strong>Freeプラン</strong>： 500リソースまで無料 ※参考</li>
<li><strong>Essentialsプラン</strong>： $0.10 &#x2F; 月</li>
<li><strong>Standardプラン</strong>： $0.47 &#x2F; 月</li>
<li><strong>Premiumプラン</strong>： $0.99 &#x2F; 月</li>
</ul>
<p>※HCP Terraformの課金体系は、IBMによる買収後、プランの統合や単価の見直しが随時行われています。導入検討時には必ず最新の公式価格表を確認してください。</p>
<p>例えば、<strong>Standardプラン</strong>を利用した場合のコスト概算は以下のようになります。</p>
<ul>
<li><strong>1,000リソース</strong>を管理した場合：月額約 <strong>$470</strong></li>
<li><strong>5,000リソース</strong>を管理した場合：月額約 <strong>$2,350</strong><br>※（管理リソース数-500）× $0.47で算出</li>
</ul>
<h3 id="コストの落とし穴（リソース爆発の罠）">コストの落とし穴（リソース爆発の罠）</h3><p>上記のコスト感（1リソースごとに課金される点）を踏まえた上で、Cloud Run Functionsの関数をデプロイする場合を考えてみましょう。実は1つの関数を動かすために、以下のような複数のリソースが必要になります。</p>
<ul>
<li>Cloud Run Functionsの関数本体</li>
<li>実行用のIAMサービスアカウント</li>
<li>権限周りのロールバインディング</li>
<li>イベント駆動のためのトリガー</li>
<li>ソースコード配置用のバケット</li>
</ul>
<p>つまり、1つの関数を追加するだけで、Terraform上では複数個のリソースを消費してしまいます。<br>また、関数に関連するリソースを追加しようとした場合、関数の個数に比例してリソース数が増えます。<br>マイクロサービス化で関数が100個、200個と増えていけば、管理リソース数はあっという間に跳ね上がり、気づいた時にはコストがとんでもないことに……という「リソース爆発」が現実味を帯びてきます。</p>
<h3 id="解決策：あえて「コンテナ」に寄せるという選択">解決策：あえて「コンテナ」に寄せるという選択</h3><p>この罠を回避するためには、ツールの使い方だけでなく、アーキテクチャそのものをHCP Terraformの課金モデルに寄せて設計するという逆転の発想が有効です。</p>
<p>例えば、細かな関数を並べる代わりに、Cloud Runを採用して複数のエンドポイントを1つのコンテナに集約する構成です。<br>これにより、Terraformで管理する対象は「Cloud Runの定義（数個のリソース）」に抑えつつ、中身のアプリケーション更新はCI&#x2F;CDで回す、といった具合に「インフラ管理の堅牢性」と「コスト効率」を両立できます。</p>
<p>「ツールで何でも管理する」のではなく、「ツールの特性（課金モデルなど）に合わせてアーキテクチャ全体を柔軟にデザインする」という視点も、システム設計時のヒントにしていただければ幸いです。</p>
<h2 id="5-まとめ">5. まとめ</h2><p>今回は、TerraformとHCP Terraform（旧Terraform Cloud）の基本的な概要から、サーバーレス環境で利用する際のコストに関する注意点までをご紹介しました。</p>
<p>HCP Terraformはチーム開発において非常に優秀なプラットフォームですが、ツールの特性（特に管理リソース数ベースの課金モデル）と自社のアーキテクチャの相性を理解した上で導入することが重要です。これから導入しようとしている方の参考になれば幸いです。</p>
]]></content>
    <summary type="html">インフラストラクチャをコードで管理する「Terraform」と、そのマネージドサービスである「Terraform Cloud（現：HCP Terraform）」についての入門的な話をします。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="TerraformCloud" scheme="https://future-architect.github.io/tags/TerraformCloud/"/>
    <category term="サーバーレス" scheme="https://future-architect.github.io/tags/%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%83%AC%E3%82%B9/"/>
  </entry>
  <entry>
    <title>Terraform連載2026を開始します</title>
    <link href="https://future-architect.github.io/articles/20260518a/"/>
    <id>https://future-architect.github.io/articles/20260518a/</id>
    <published>2026-05-17T15:00:00.000Z</published>
    <updated>2026-05-17T15:00:00.000Z</updated>
    <author><name>伊藤太斉</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260518a/top.jpg" alt="" width="800" height="436">

<p>こんにちは。技術ブログ運営の伊藤です。</p>
<p>今年もフューチャー技術ブログでは、Terraformを題材とした連載を開催します。今回は8人の方が参加します。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 日付 / 執筆者 / タイトル &#x2F; テーマ"><table>
<thead>
<tr>
<th>日付</th>
<th>執筆者</th>
<th>タイトル &#x2F; テーマ</th>
</tr>
</thead>
<tbody><tr>
<td>5&#x2F;18（月）</td>
<td>伊藤太斉さん</td>
<td>インデックス+最近のリリースまとめ（この記事です）</td>
</tr>
<tr>
<td><img src="/images/2026/20260519a/thumbnail.png" alt="" width="72" height="48" loading="lazy"> 5&#x2F;19（火）</td>
<td>大江悠斗さん</td>
<td>Artifact Registry利用料金の最適化方針と対応手順</td>
</tr>
<tr>
<td><img src="/images/2026/20260521a/thumbnail.jpg" alt="" width="72" height="48" loading="lazy"> 5&#x2F;21（木）</td>
<td>片岡久人さん</td>
<td>Terraform Cloudやってみた</td>
</tr>
<tr>
<td><img src="/images/2026/20260522a/thumbnail.png" alt="" width="72" height="48" loading="lazy"> 5&#x2F;22（金）</td>
<td>八木雅斗さん</td>
<td>Terraform経験者が初めてAWS CDKを利用して感じたギャップ</td>
</tr>
<tr>
<td>—</td>
<td>—</td>
<td>—</td>
</tr>
<tr>
<td><img src="/images/2026/20260526a/thumbnail.jpg" alt="" width="72" height="48" loading="lazy"> 5&#x2F;26（火）</td>
<td>棚井龍之介さん</td>
<td>Terraform × Policy as Code:管理策を実装し、追跡可能なガードレールへ</td>
</tr>
<tr>
<td><img src="/images/2026/20260527a/thumbnail.jpg" alt="" width="72" height="48" loading="lazy"> 5&#x2F;27（水）</td>
<td>香村真紀さん</td>
<td>Terraform で AWS SAM CLI を利用して、ローカルで Lambda のテストとデプロイを試してみた</td>
</tr>
<tr>
<td><img src="/images/2026/20260528a/thumbnail.png" alt="" width="72" height="48" loading="lazy"> 5&#x2F;28（木）</td>
<td>市川裕也さん</td>
<td>moto に Terraform を打ち込み、基本的なイベント駆動の構成を動かしてみた</td>
</tr>
<tr>
<td><img src="/images/2026/20260601a/thumbnail.jpg" alt="" width="72" height="48" loading="lazy"> 6&#x2F;1（月）</td>
<td>永井辰弥さん</td>
<td>Terraformに関してAIに聞いてみて初心者目線で疑問点を公式ドキュメントで解消してみた</td>
</tr>
</tbody></table></div>
<p>Terraform連載は2023年から始まり、4年連続の開催となります。技術カットの連載企画の中では比較的ご長寿連載になりました。</p>
<ul>
<li>Terraform連載2023</li>
<li>Terraform連載2024</li>
<li>Terraform連載2025</li>
</ul>
<p>この記事では、昨年のTerraform連載2025以降にリリースされたv1.12からこのブログの公開日の少し前にリリースされたv1.15までの新機能に触れます。</p>
<h2 id="バージョン別変更点一覧">バージョン別変更点一覧</h2><p>v1.12からv1.15までのまとめを生成AIに作ってもらいました。特に、ここでは新機能と機能改善について着目します。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 追加されたバージョン / カテゴリ / 内容"><table>
<thead>
<tr>
<th>追加されたバージョン</th>
<th>カテゴリ</th>
<th>内容</th>
</tr>
</thead>
<tbody><tr>
<td>v1.12</td>
<td>新機能</td>
<td>OCI Object Storage バックエンドの正式追加</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>import</code> ブロックで <code>id</code> に加え <code>identity</code> 属性をサポート（両者は排他）</td>
</tr>
<tr>
<td></td>
<td>改善</td>
<td><code>terraform test</code> コマンドに <code>-parallelism=n</code> フラグを追加。並列テスト実行が可能に</td>
</tr>
<tr>
<td></td>
<td></td>
<td>論理演算子（<code>&amp;&amp;</code> &#x2F; <code>||</code>）の短絡評価（short-circuit）に対応</td>
</tr>
<tr>
<td></td>
<td></td>
<td>UI の経過時間表示を <code>mm:ss</code> 形式に改善</td>
</tr>
<tr>
<td></td>
<td></td>
<td>テストアサーション失敗時の診断情報が詳細化</td>
</tr>
<tr>
<td>v1.13</td>
<td>新機能</td>
<td>module の <code>source</code> &#x2F; <code>version</code> 属性で変数（<code>var.*</code>）や <code>local.*</code> が使用可能に</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>terraform stacks</code> コマンドが追加され、CLI から直接 Stacks 操作が可能に</td>
</tr>
<tr>
<td></td>
<td>改善</td>
<td><code>terraform test</code> の <code>mock</code> ブロック内で関数（functions）が使用可能に</td>
</tr>
<tr>
<td></td>
<td></td>
<td>プロバイダーが設定した非推奨（deprecated）メッセージが警告として表示されるように</td>
</tr>
<tr>
<td></td>
<td></td>
<td>sensitive としてマークされたネストモジュール出力が正しく伝播するバグを修正</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>rpcapi</code> コマンドが GA（一般提供）に昇格</td>
</tr>
<tr>
<td>v1.14</td>
<td>新機能</td>
<td><strong>List Resources</strong>：<code>*.tfquery.hcl</code> ファイルで定義し、既存インフラへのクエリ・フィルタリングが可能に</td>
</tr>
<tr>
<td></td>
<td></td>
<td><strong><code>terraform query</code> コマンド</strong>：リスト操作の実行、結果から import 用設定の自動生成が可能</td>
</tr>
<tr>
<td></td>
<td></td>
<td><strong><code>actions</code> ブロック</strong>：CRUD モデル外の命令的操作（例：<code>aws_lambda_invoke</code>、<code>aws_cloudfront_create_invalidation</code>）をリソースのライフサイクルにバインド可能</td>
</tr>
<tr>
<td></td>
<td>改善</td>
<td><code>GenerateResourceConfiguration</code> RPC が追加され、インポート時により精密な設定値を生成可能に</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>terraform import</code> でワークスペース変数セットから継承した変数も取得されるように</td>
</tr>
<tr>
<td>v1.15</td>
<td>新機能</td>
<td><strong>Windows ARM64</strong> ビルドを正式提供。Snapdragon 搭載 Windows マシンでネイティブ動作可能に</td>
</tr>
<tr>
<td></td>
<td></td>
<td><strong><code>deprecated</code> 属性</strong>が <code>variable</code> &#x2F; <code>output</code> ブロックで使用可能に。非推奨の変数に値を渡したり非推奨のアウトプットを参照した際に警告を発生させられる</td>
</tr>
<tr>
<td></td>
<td></td>
<td><strong><code>convert()</code> 関数</strong>の追加。インライン型変換を精密に行えるように</td>
</tr>
<tr>
<td></td>
<td></td>
<td><strong>S3 バックエンド</strong>で <code>aws login</code> 認証をサポート</td>
</tr>
<tr>
<td></td>
<td></td>
<td>module の <code>source</code> &#x2F; <code>version</code> 属性で変数・locals を使用可能に（動的モジュールソース）※1.13 で追加された機能の安定版として正式統合</td>
</tr>
<tr>
<td></td>
<td>改善</td>
<td><code>terraform validate</code> コマンドがバックエンドブロックも検証するようになり、バックエンドの型・必須属性・固有バリデーションロジックまでチェック</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>terraform init</code> でのプロバイダーインストール処理をリファクタリング。init の処理順序が変わり 2 つの新ログメッセージが追加（ログをプログラムでパースしている場合は要確認）</td>
</tr>
<tr>
<td></td>
<td></td>
<td>Terraform Cloud &#x2F; Registry の検出ネットワークリクエストがより堅牢になり、一時的なネットワーク障害時のエラーが減少</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>terraform fmt</code> が <code>.tfquery.hcl</code> ファイルのフォーマットに対応</td>
</tr>
</tbody></table></div>
<p>一通り眺めてみると、これまではTerraformでリソースを作るときにフォーカスされる機能が多い印象でしたが、よりテスト、その後の保守性まで踏まえたバージョンアップをしているように感じました。</p>
<h2 id="気になる機能">気になる機能</h2><p>ここからは特に私が気になった機能について、2つほどピックアップしました。</p>
<h3 id="v1-12-terraform-testコマンドで-parallelism-nフラグが利用可能になった">v1.12: <code>terraform test</code>コマンドで<code>-parallelism=n</code>フラグが利用可能になった</h3><p>これまで、Terraformの<code>plan</code>コマンドや<code>apply</code>コマンドで使えたparallelismのフラグが<code>test</code>コマンドでも使うことができるようになりました。このフラグは、Terraformのそれぞれのコマンドを実行するときの並列度を調整でき、デフォルト値は<code>plan</code>コマンドや<code>apply</code>コマンドと同様に10に設定されています<sup id="fnref:1">1</sup>。</p>
<p>ユースケースとしては、あるTerraformでの管理対象が増大したときにparallelismのフラグをつけることで、それなりに高速化させることが可能です。ただ、高速化する一方、その管理対象の粒度が適切でない場合（数百のリソースになる場合）はリファクタリングも視野に入れましょう。</p>
<h3 id="v1-14-terraform-queryコマンドを利用してリスト操作の実行、結果から-import-用設定の自動生成が可能になった">v1.14: <code>terraform query</code>コマンドを利用してリスト操作の実行、結果から <code>import</code> 用設定の自動生成が可能になった</h3><p>v1.5で<code>import</code>ブロックがサポートされ、複数のリソースを.tfファイルに定義してCI&#x2F;CD上でリソースのインポートが可能でした。しかしながらインポートする対象のリソースはものによってはIDであったり、ARNであったり定義するべきものが異なります。そのため<code>import</code>ブロックの記述は煩雑なものでした。</p>
<p>ここで、v1.14で新たに使えるようになった<code>terraform query</code>コマンドを使うことでソース記述のハードルがグッと下がりました。</p>
<p>例えば、EC2インスタンスを手動で立てたのち、取り込みたい場合においては以下のような<code>list</code>ブロックを<code>.tfquery.hcl</code>ファイルに記載します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-z4hl3b-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-z4hl3b-1" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre>list <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;development&quot;</span> {
  <span class="keyword">provider</span> = aws
}</pre></td></tr></tbody></table></figure></div>

<p><code>resource</code>ブロックに対応するリソース（今回は<code>aws_instance</code>）を記述することで、Terraformで管理されている、されていないに関わらず一覧化できます。実際に<code>terraform query</code>コマンドを実行すると、以下のように<code>list</code>ブロックで定義したリソースの一覧を取得できます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-z4hl3b-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-z4hl3b-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform query</span></span><br><span class="line">list.aws_instance.development   account_id=123456789012,id=i-xxxxxx,region=ap-northeast-1   dev-instance (i-xxxxxx)</span><br><span class="line">list.aws_instance.development   account_id=123456789012,id=i-yyyyyy,region=ap-northeast-1   query-test-instance (i-yyyyyy)</span><br></pre></td></tr></table></figure></div>

<p>また、特定の範囲に絞りたい場合にはタグなどで絞ることができ、例としてNameタグで絞ると以下のような書き方になり、1行目のリソースのみ抽出できます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-z4hl3b-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-z4hl3b-3" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre>list <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;development&quot;</span> {
  <span class="keyword">provider</span> = aws
<span class="line"></span>
  config {
    filter {
      name   = <span class="string">&quot;tag:Name&quot;</span>
      values = [<span class="string">&quot;dev-instance&quot;</span>]
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<p>対象のリソースを選択した後は<code>terraform query</code>コマンドに<code>-generate-config-out</code>オプションを指定して実行すれば、<code>resource</code>ブロックと<code>import</code>ブロックを自動生成してくれます（今回はconfigブロックがない状態で生成しました）。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-z4hl3b-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-z4hl3b-4" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># __generated__ by Terraform</span>
<span class="comment"># Please review these resources and move them into your main configuration files.</span>
<span class="line"></span>
<span class="comment"># __generated__ by Terraform</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;development_0&quot;</span> {
  <span class="keyword">provider</span>   = aws
  ...
  private_ip = <span class="string">&quot;10.0.2.92&quot;</span>
  region     = <span class="string">&quot;ap-northeast-1&quot;</span>
  tags = {
    Name = <span class="string">&quot;dev-instance&quot;</span>
  }
  ...
}
<span class="line"></span>
import {
  to       = aws_instance.development_0
  <span class="keyword">provider</span> = aws
  identity = {
    account_id = <span class="string">&quot;123456789012&quot;</span>
    id         = <span class="string">&quot;i-xxxxxx&quot;</span>
    region     = <span class="string">&quot;ap-northeast-1&quot;</span>
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;development_1&quot;</span> {
  <span class="keyword">provider</span>   = aws
  ...
  private_ip = <span class="string">&quot;10.0.2.228&quot;</span>
  region     = <span class="string">&quot;ap-northeast-1&quot;</span>
  tags = {
    Name = <span class="string">&quot;query-test-instance&quot;</span>
  }
  ...
}
<span class="line"></span>
import {
  to       = aws_instance.development_1
  <span class="keyword">provider</span> = aws
  identity = {
    account_id = <span class="string">&quot;123456789012&quot;</span>
    id         = <span class="string">&quot;i-yyyyyy&quot;</span>
    region     = <span class="string">&quot;ap-northeast-1&quot;</span>
  }
}</pre></td></tr></tbody></table></figure></div>

<p>後は生成されたソースを期待する状態に修正、配置することでTerraformでの管理が可能となります。</p>
<h2 id="さいごに">さいごに</h2><p>改めて直近のリリースノートを読み返して機能を触ってみましたが、着実に機能追加がされており、便利な関数も増えたことで、運用・エンハンスという観点でも便利になっていることを感じました。</p>
<p>この連載で社内のいろんな知見が公開されるのでぜひ皆さんも楽しみにしていただけると幸いです。</p>
<p>引き続き、よろしくお願いします。</p>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">terraform apply command</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">今年もフューチャー技術ブログでは、Terraformを題材とした連載を開催します。本記事ではv1.12からv1.15までの新機能や機能改善の振り返りと、今年の連載スケジュールについて案内します。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="インデックス" scheme="https://future-architect.github.io/tags/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9/"/>
  </entry>
  <entry>
    <title>Terraformの基本のキ！ファイル構成からコマンドまでやさしく解説</title>
    <link href="https://future-architect.github.io/articles/20250516a/"/>
    <id>https://future-architect.github.io/articles/20250516a/</id>
    <published>2025-05-15T15:00:00.000Z</published>
    <updated>2025-05-15T15:00:00.000Z</updated>
    <author><name>染矢幸子</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250516a/image.png" alt="" width="976" height="269">

<p>春の入門祭り2025 17本目の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>はじめまして、TIG（Techonology Innovation Group）の染矢です。</p>
<p>2025 年 2 月から新卒として事業部に配属され、インフラ周りを担当しています。IT 未経験での入社だったため、インフラって何？ というところから始まり、AWS や Terraform についてこの数か月間学んできました。</p>
<p>未経験者がインフラ業務を数か月間経験し、最初の頃理解が難しかった点や、躓いた点をご紹介していきます。</p>
<h2 id="1-Terraform-ってなに">1. Terraform ってなに</h2><p>Infrastructure as Code (IaC) ツールの一種で、クラウドやオンプレミス環境のインフラリソースをコードで定義し、自動的にプロビジョニング、管理できるツールです。</p>
<p>プロビジョニングの詳しい記事:【Terraform】プロビジョニングとはなんぞや？</p>
<h3 id="1-2-IaC-を使う理由">1.2 IaC を使う理由</h3><p>マネージドコンソール等でポチポチとインフラを構築することも勿論できますが、laC を使うことで次のようなメリットがあります。</p>
<ol>
<li>変更管理が容易</li>
<li>間違えても、最新の IaC を apply することで元の状態を復元できる</li>
<li>対応漏れの検知や横展開調査がやりやすくなる</li>
</ol>
<p>チームでの開発ではインフラの構成を全員が理解しているわけではないので、Git で履歴管理し可視化することが大切です。</p>
<p>また、セキュリティ的に甘い設定があったとしても、いつからいつまでの期間で甘い設定だったから影響範囲は～と特定がしやすくなります。</p>
<h2 id="2-全体像">2. 全体像</h2><h3 id="2-1-ファイル構成">2.1 ファイル構成</h3><div class="note-container note-info"><span class="note-icon"></span><div>

<p>構成に関してあくまでもわかりやすい構成図を描いています。開発環境によって異なることをご留意ください。</p>
</div></div>

<p>基本的には <code>main.tf</code> を軸にインフラを構築していきます。<br>module 配下のディレクトリでリソースタイプ毎に分けていきます。</p>
<h3 id="開発環境が一つの場合">開発環境が一つの場合</h3><figure class="highlight sh"><figcaption><span>project1</span></figcaption><table><tr><td class="code"><pre><span class="line">project1</span><br><span class="line">├── locals.tf</span><br><span class="line">├── main.tf</span><br><span class="line">├── output.tf</span><br><span class="line">├── README.md</span><br><span class="line">└── module</span><br><span class="line">    ├── ec2</span><br><span class="line">    │   ├── ec2_instance.tf</span><br><span class="line">    │   └── variables.tf</span><br><span class="line">    ├── s3</span><br><span class="line">    │   ├── s3_bucket.tf</span><br><span class="line">    │   └── variables.tf</span><br><span class="line">    └── vpc</span><br><span class="line">        ├── variables.tf</span><br><span class="line">        └── vpc.tf</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="開発環境が複数の場合">開発環境が複数の場合</h3><p>検証環境や本番環境に分ける場合は下記のようなファイル構成を組むとわかりやすいです。</p>
<figure class="highlight sh"><figcaption><span>project2</span></figcaption><table><tr><td class="code"><pre><span class="line">project2</span><br><span class="line">├── <span class="built_in">env</span></span><br><span class="line">│   ├── dev</span><br><span class="line">│   │   ├── locals.tf</span><br><span class="line">│   │   ├── main.tf</span><br><span class="line">│   │   ├── output.tf</span><br><span class="line">│   │   └── README.md</span><br><span class="line">│   ├── prod</span><br><span class="line">│   │   ├── locals.tf</span><br><span class="line">│   │   ├── main.tf</span><br><span class="line">│   │   ├── output.tf</span><br><span class="line">│   │   └── README.md</span><br><span class="line">│   └── stg</span><br><span class="line">│       ├── locals.tf</span><br><span class="line">│       ├── main.tf</span><br><span class="line">│       ├── output.tf</span><br><span class="line">│       └── README.md</span><br><span class="line">└── module</span><br><span class="line">    ├── ec2</span><br><span class="line">    │   ├── ec2_instance.tf</span><br><span class="line">    │   └── variables.tf</span><br><span class="line">    ├── s3</span><br><span class="line">    │   ├── s3_bucket.tf</span><br><span class="line">    │   └── variables.tf</span><br><span class="line">    └── vpc</span><br><span class="line">        ├── variables.tf</span><br><span class="line">        └── vpc.tf</span><br></pre></td></tr></table></figure>

<p>公式のガイドライン、 Standard Module Structure | Terraform | HashiCorp Developer にもファイル構成についての記載があります。</p>
<h3 id="2-2-モジュールの使い方">2.2 モジュールの使い方</h3><p>ここからはモジュールの使い方について説明していきます（基本的に複数の環境があることを想定しています）。</p>
<p>Terraform ではモジュールで作った各リソースを main に呼び出してインフラを構築していきます。そのため環境ごとに呼び出すモジュールを変えたり、リソースのパラメータの設定値を変更できます。</p>
<p>例えばモジュール EC2 の中に EC2 インスタンスのリソースを次の様に作成します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-11m9pmh-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-11m9pmh-1" title="コードの折り返しを切り替える"></label><figcaption><span>module/EC2/ec2_instance.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;example&quot;</span> {
  ami                         = var.ami_id
  instance_type               = var.instance_type
}</pre></td></tr></tbody></table></figure></div>

<p><code>var.ami_id</code> や <code>var.instance_type</code> ってなってますよね？</p>
<p>この var は ↓ の <code>variables.tf</code> で定義してますよという意味です。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-11m9pmh-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-11m9pmh-2" title="コードの折り返しを切り替える"></label><figcaption><span>module/EC2/valicables.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">variable</span> <span class="string">&quot;ami_id&quot;</span> {
  type        = string
  description = <span class="string">&quot;A String of ami_id&quot;</span>
}
<span class="keyword">variable</span> <span class="string">&quot;instance_type&quot;</span> {
  type        = string
  description = <span class="string">&quot;A String of instance_type&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<p><code>variables.tf</code> で定義してるって言ったのに、string と説明欄しかない！ ってなりますが、環境ごとに値を変えたい場合は実際に値を入れるのは main.tf になります。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-11m9pmh-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-11m9pmh-3" title="コードの折り返しを切り替える"></label><figcaption><span>project2/dev/main.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">module</span> <span class="string">&quot;EC2&quot;</span> {
source = <span class="string">&quot;../../module/EC2&quot;</span>
  ami                         = <span class="string">&quot;ami-✖✖✖✖✖✖✖✖✖&quot;</span>
  instance_type               = <span class="string">&quot;t3.medium&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<p><code>main.tf</code> で実際の値を入れることで、各環境ごとにパラメーターを変更できます。</p>
<p>そのため流れは…</p>
<ol>
<li>モジュール内でリソースを作成する</li>
<li>variables.tf で定義する</li>
<li>main.tf で値を入れる</li>
</ol>
<p>…となります。</p>
<p>※ちなみに、<code>variables.tf</code> に書いた変数はモジュールを呼び出す際、 <code>main.tf</code> で定義をしないとエラーになります。</p>
<h2 id="3-ブロックタイプ">3. ブロックタイプ</h2><p>Terraform には様々なブロックタイプがあります。</p>
<p>このうち主に使うブロックタイプについて解説していきます。</p>
<p>私は最初の頃 resource と data の違いがよくわかっていませんでした。🤔</p>
<h3 id="3-1-resource">3.1 resource</h3><p>おそらく Terraform で一番触れるブロックタイプだと思います。</p>
<ul>
<li><p><strong>目的・役割</strong><br>実際に作成・管理したいインフラストラクチャの構成要素 (例: EC2 インスタンス、S3 バケット、VPC など) を定義します。Terraform はこのブロックに基づいてリソースを計画し、適用 (作成、更新、削除) します。</p>
</li>
<li><p><strong>リソースタイプ</strong><br>プロバイダーが提供するリソースの種類（例えば、AWS の EC2 インスタンスなら aws_instance）</p>
</li>
<li><p><strong>リソース名</strong><br>Terraform の設定ファイル内でこのリソースを一意に識別するための名前。この名前を使って他の場所から参照するため、必ず一意のものにしましょう。</p>
<figure class="highlight tf"><figcaption><span>例</span></figcaption><table><tbody><tr><td class="code"><pre>  <span class="keyword">resource</span> <span class="string">&quot;リソースタイプ&quot;</span> <span class="string">&quot;リソース名&quot;</span> { ... }</pre></td></tr></tbody></table></figure></li>
</ul>
<h3 id="3-2-data">3.2 data</h3><p><strong>目的・役割</strong></p>
<p>Terraform の設定の外部にある既存のリソースの情報を読み込んで利用するために使います。Terraform は data ブロックで定義されたものを管理 (作成・変更・削除) しません。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-11m9pmh-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-11m9pmh-4" title="コードの折り返しを切り替える"></label><figcaption><span>例</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">data</span> <span class="string">&quot;データソースタイプ&quot;</span> <span class="string">&quot;データソース名&quot;</span> {
  // データをフィルタリング引数
}</pre></td></tr></tbody></table></figure></div>

<p>data ブロックについては、私が躓いた部分でもあるので次の１～ 2 のユースケースを元に詳しく解説します。</p>
<h4 id="ユースケース-1-AMI-ID-の取得">ユースケース 1: AMI ID の取得</h4><p>OS のイメージ（AMI）を取得します。filter で名前やタイプを検索でき、当てはまる AMI を取得できます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-11m9pmh-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-11m9pmh-5" title="コードの折り返しを切り替える"></label><figcaption><span>例</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">data</span> <span class="string">&quot;aws_ami&quot;</span> <span class="string">&quot;example&quot;</span> {
  most_recent = true
  owners      = [<span class="string">&quot;amazon&quot;</span>]
  filter {
    name   = <span class="string">&quot;name&quot;</span>
    values = [<span class="string">&quot;✖✖✖✖✖✖✖✖✖✖✖&quot;</span>] /名前でAMIをフィルタリング/
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;web_server&quot;</span> {
  ami           = <span class="keyword">data</span>.aws_ami.example.id
  instance_type = <span class="string">&quot;t2.micro&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<h4 id="ユースケース-2-IAM-policy-の簡潔化">ユースケース 2: IAM policy の簡潔化</h4><p>IAM ポリシーは JSON 形式ですが、resource ブロック内で複雑なポリシーを JSON で直接記述・管理するのは難しいです。</p>
<p>また data ブロックにすることで可読性も期待できるため、何らかのポリシーを作成する際は、data ブロックに分けて作成することをお勧めします。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-11m9pmh-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-11m9pmh-6" title="コードの折り返しを切り替える"></label><figcaption><span>例</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">data</span> <span class="string">&quot;aws_iam_policy_document&quot;</span> <span class="string">&quot;example_policy_document&quot;</span> {
  statement {
    actions   = [<span class="string">&quot;s3:*&quot;</span>]
    resources = [<span class="string">&quot;arn:aws:s3:::example-bucket&quot;</span>]
    effect    = <span class="string">&quot;Allow&quot;</span>
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_iam_policy&quot;</span> <span class="string">&quot;example_policy&quot;</span> {
  name   = <span class="string">&quot;ExamplePolicy&quot;</span>
  policy = <span class="keyword">data</span>.aws_iam_policy_document.example_policy_document.json
}</pre></td></tr></tbody></table></figure></div>

<h3 id="3-3-variable">3.3 variable</h3><p>いわゆる変数ブロックです。</p>
<p><strong>目的・役割</strong></p>
<p>Terraform の設定を柔軟にするために、外部から値を注入したり、設定内で再利用可能な値を定義します。また同じ Terraform コードを異なる環境（検証、本番など）や異なる目的で利用する際に、変数値を変えるだけで対応できます。</p>
<p>基本的には下記のような形になります。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-11m9pmh-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-11m9pmh-7" title="コードの折り返しを切り替える"></label><figcaption><span>例</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">variable</span> <span class="string">&quot;変数名&quot;</span> {
  type        = &lt;型&gt;
  description = <span class="string">&quot;&lt;変数の説明&gt;&quot;</span>
  default     = &lt;デフォルト値&gt;
}</pre></td></tr></tbody></table></figure></div>

<h3 id="3-4-output">3.4 output</h3><p><strong>目的・役割</strong></p>
<p>Terraform が管理するリソースから得られる情報 (例: 作成された EC2 インスタンスの IP アドレス、S3 バケットの名前など) を、<code>terraform apply</code> の実行後に出力したり、他の Terraform 設定から参照できるようになります。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-11m9pmh-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-11m9pmh-8" title="コードの折り返しを切り替える"></label><figcaption><span>例</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">output</span> <span class="string">&quot;出力名&quot;</span> {
  value       = &lt;出力したい値&gt;
  description = <span class="string">&quot;&lt;出力の説明&gt;&quot;</span>
  sensitive   = &lt;true または false&gt;
}</pre></td></tr></tbody></table></figure></div>

<ul>
<li><strong>value</strong><br>実際に出力される値を指定します。<br>(例: <code>aws_instance.example.public_ip</code> <code>module.vpc.vpc_id</code>)</li>
<li><strong>description</strong><br>説明欄です。チームで開発する際は必須です。</li>
<li><strong>sensitive</strong><br><code>true</code> に設定すると、<code>terraform apply</code> の出力や <code>terraform output</code> コマンドの通常表示では、実際の値の代わりに <code>&lt;sensitive&gt;</code> と表示されます。これにより、機密情報がターミナルログに表示されるのを防ぎます。デフォルトは <code>false</code> です。</li>
</ul>
<h2 id="4-Terraform-コマンド">4. Terraform コマンド</h2><p>Terraform で書いたコードで実際にインフラを構築する際のコマンドについて解説します。</p>
<p>※Terraform コマンドはカレントディレクトリ配下を全て実行します。</p>
<p>そのため複数の環境の内、1つを実行したい場合はその環境の階層に行きましょう。</p>
<p>下記の例で本番環境を作成・更新したい場合、project2&#x2F;env&#x2F;prod の階層に行きましょう。</p>
<figure class="highlight sh"><figcaption><span>project2</span></figcaption><table><tr><td class="code"><pre><span class="line">project2</span><br><span class="line">├── <span class="built_in">env</span></span><br><span class="line">│   ├── dev</span><br><span class="line">│   │   ├── locals.tf</span><br><span class="line">│   │   ├── main.tf</span><br><span class="line">│   │   ├── output.tf</span><br><span class="line">│   │   └── README.md</span><br><span class="line">│   ├── prod</span><br><span class="line">│   │   ├── locals.tf</span><br><span class="line">│   │   ├── main.tf</span><br><span class="line">│   │   ├── output.tf</span><br><span class="line">│   │   └── README.md</span><br><span class="line">│   └── stg</span><br><span class="line">│       ├── locals.tf</span><br><span class="line">│       ├── main.tf</span><br><span class="line">│       ├── output.tf</span><br><span class="line">│       └── README.md</span><br><span class="line">└── module</span><br><span class="line">    ├── ec2</span><br><span class="line">    │   ├── ec2_instance.tf</span><br><span class="line">    │   └── variables.tf</span><br><span class="line">    ├── s3</span><br><span class="line">    │   ├── s3_bucket.tf</span><br><span class="line">    │   └── variables.tf</span><br><span class="line">    └── vpc</span><br><span class="line">        ├── variables.tf</span><br><span class="line">        └── vpc.tf</span><br></pre></td></tr></table></figure>

<h3 id="4-1-terraform-init">4.1 terraform init</h3><ol>
<li><strong>バックエンドの初期化</strong><br>Terraform は、管理するインフラの状態を <strong>状態ファイル（state file、通常は terraform.tfstate）</strong> に保存します。<br>init は状態ファイルを読み書きできるように準備します。</li>
</ol>
<div class="note-container note-info"><span class="note-icon"></span><div>

<p>状態ファイルとは？Terraform が管理するインフラストラクチャの現在の実際の状態を記録・追跡するための重要なファイル</p>
</div></div>

<ol start="2">
<li><strong>プロバイダープラグインのダウンロード</strong><br>Terraform は、さまざまなクラウドプロバイダー (AWS, Azure, GCP など) と連携するために「プロバイダープラグイン」を使用します。init は設定ファイル（provider ブロックや terraform ブロック内の required_providers）を元に、必要なプロバイダープラグインをダウンロードします。</li>
<li><strong>モジュールの取得</strong><br>設定ファイル内で module ブロックを使って外部のモジュールを参照している場合、init はそれらのモジュールを指定されたソースからダウンロードまたはコピーします。</li>
<li><strong>依存関係ロックファイルの作成&#x2F;更新</strong><br>init を実行すると、<code>.terraform.lock.hcl</code> というファイルが作成または更新されます。<br>このファイルには、選択されたプロバイダーの正確なバージョンとハッシュ値が記録されます。これにより、チームメンバー間で同じバージョンのプロバイダーを使用することが保証され、環境による意図しない挙動の違いを防ぎます。</li>
</ol>
<h3 id="4-2-terraform-plan">4.2 terraform plan</h3><ol>
<li><strong>変更内容の予測</strong><br>現在の Terraform 設定ファイルが示す「望ましい状態」と、現在のインフラの「実際の状態」を比較します。</li>
<li><strong>実行計画の表示</strong><br>差分に基づいて、実行計画を生成します。<br>変更の概要（例: Plan: 1 to add, 2 to change, 1 to destroy.）が表示されます。</li>
</ol>
<h3 id="4-3-terraform-apply">4.3 terraform apply</h3><p>実際にインフラが変わるコマンドです。慎重に使いましょう。</p>
<ol>
<li><strong>変更の実行</strong><br>Terraform は実行計画に従って、プロバイダープラグインを通じてクラウド API などを呼び出し、リソースの作成、更新、または削除します。</li>
<li><strong>状態ファイルの更新</strong><br>全ての変更が完了すると、Terraform は状態ファイル (terraform.tfstate) を更新し、最新のインフラストラクチャの状態を反映します。Terraform 101 記事に詳しく記載されているのでぜひ一読ください！</li>
</ol>
<h2 id="5-Terraform-勉強法">5. Terraform 勉強法</h2><h3 id="5-1-公式ドキュメント">5.1 公式ドキュメント</h3><p>Terraform は公式ドキュメントにリソースの作成方法や使い方が詳しく書いてあります。</p>
<p>リソースを作成する際は必ず公式ドキュメントを確認しましょう！</p>
<p>公式ドキュメントを見ても、英語だしパラメータが多くて読み飛ばしてしまいたくなりますが、最初の頃は時間をかけてでも、<strong>一文ずつ理解</strong>することが大切だと感じています。</p>
<p>また勉強する中で、下記の記事がとても分かりやすかったのでお勧めです。</p>
<p>Terraform に入門して 1 ヶ月経ったので、初心者が気をつけるべきポイントを書いてみる</p>
<h3 id="5-2-おすすめ参考書">5.2 おすすめ参考書</h3><p>最後に私が Terraform を学ぶ上で役に立ったと思う参考書についてご紹介します。</p>
<p>「実践 Terraform AWS におけるシステム設計とベストプラクティス」は、AWS 環境前提でありますが、基本的に使うリソースの使い方を詳しく載せてくれています。</p>

<img src="/images/2025/20250516a/image_2.png" alt="image.png" width="340" height="480" loading="lazy">

<p>実践 Terraform 　 AWS におけるシステム設計とベストプラクティス (技術の泉シリーズ（NextPublishing）)<br></p>
<h2 id="おわりに">おわりに</h2><p>ここまで読んでいただきありがとうございました！</p>
<p>Terraform 超入門ということで、Terraform ってなに？ というところからご紹介をしていきました。</p>
<p>今後も初心者向けの記事を投稿していきたいと思います！</p>
]]></content>
    <summary type="html">IT未経験での入社だったため、インフラって何？というところから始まり、AWSやTerraformについてこの数か月間学んできました。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="入門" scheme="https://future-architect.github.io/tags/%E5%85%A5%E9%96%80/"/>
    <category term="初心者向け" scheme="https://future-architect.github.io/tags/%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91/"/>
  </entry>
  <entry>
    <title>Azure BicepリンターでBicepコードを最新化：効率的なリファクタリング手法</title>
    <link href="https://future-architect.github.io/articles/20250509b/"/>
    <id>https://future-architect.github.io/articles/20250509b/</id>
    <published>2025-05-08T15:00:01.000Z</published>
    <updated>2025-05-08T15:00:01.000Z</updated>
    <author><name>三浦克之</name></author>
    <content type="html"><![CDATA[<div class="note-container note-warn"><span class="note-icon"></span><div>

<p>グループ会社であるFutureOneの Qiita Organizationで公開された 記事 をクロスポストで公開しています。</p>
</div></div>

<h2 id="はじめに">はじめに</h2><p>Azure Bicepは、Microsoft Azureのリソースを効率的に管理するために開発された宣言型の言語です。Azure上のインフラストラクチャをコードとして定義し、デプロイが可能です。</p>
<p>Azure Bicepは便利で強力なツールですが、Azureの急速な進化に追従する必要があるため、どのようにコードを最新化していくかが悩みどころです。Azureは頻繁に新しいサービスやAPIバージョンをリリースするため、Bicepコードを最新の状態に保つためには、これらの変更に迅速に対応する必要があります。</p>
<p>本記事では、Microsoft社のツールを使ったBicepコードの効率的な最新化する方法を紹介します。</p>
<h2 id="Bicep-リンター">Bicep リンター</h2><p>Bicepコードの最新化に役立つのが、Bicepリンターです。</p>
<blockquote>
<p>Bicep リンターは、Bicep ファイルに構文エラーとベスト プラクティス違反がないかチェックします。 リンターを使用すると、開発時のガイダンスが提供され、コーディング標準を適用できます。 ファイルのチェックに使用するベスト プラクティスをカスタマイズできます。</p>
</blockquote>
<p>こちらのツールを使うことで、コードの最新化が効率的に実施できます。</p>
<h2 id="環境情報">環境情報</h2><p>今回は、<code>C:\Bicep</code> に配置した <code>main.bicep</code>* と呼び出されるモジュール群を対象にコードの最新化を行います。</p>
<img fetchpriority="high" src="/images/2025/20250509b/image.png" alt="image.png" width="981" height="413">

<h3 id="事前準備">事前準備</h3><p>最新のAPIバージョンが表示されない場合もあるため、以下のアップデートを事前に行っておきます。</p>
<figure class="highlight sh"><figcaption><span>Bicepのアップデート</span></figcaption><table><tr><td class="code"><pre><span class="line">az bicep upgrade</span><br></pre></td></tr></table></figure>

<h2 id="リンターの実行">リンターの実行</h2><p>以下コマンドでリンターを実行できます。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">az bicep lint</span><br></pre></td></tr></table></figure>

<p>今回は実施環境に合わせて以下のコマンドを実行します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">az bicep lint -f .\main.bicep</span><br></pre></td></tr></table></figure>

<ul>
<li><code>-f</code> によるbicepファイルの指定は必須です。</li>
<li><code>main.bicep</code> が参照しているモジュールファイルも対象にしてくれます。</li>
</ul>
<p>以下のようにチェック結果が表示されます。</p>
<img src="/images/2025/20250509b/image_2.png" alt="" width="1200" height="306" loading="lazy">

<p>デフォルトでは様々なチェック項目に従って結果が表示されます。</p>
<p>チャック内容をカスタマイズしたい場合は、<code>bicepconfig.json</code> を利用します。</p>
<ul>
<li>参考: Bicep 構成のリンター設定 - Azure Resource Manager | Microsoft Learn</li>
</ul>
<p>今回はAPIバージョンのみに限定したいので、<code>bicepconfig.json</code> を以下のようにします。</p>
<figure class="highlight json"><figcaption><span>bicepconfig.json</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;analyzers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;core&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;rules&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;adminusername-should-not-be-literal&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;artifacts-parameters&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;decompiler-cleanup&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;explicit-values-for-loc-params&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;max-asserts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;max-outputs&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;max-params&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;max-resources&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;max-variables&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;nested-deployment-template-scoping&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-conflicting-metadata&quot;</span> <span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-deployments-resources&quot;</span> <span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-hardcoded-env-urls&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-hardcoded-location&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-loc-expr-outside-params&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-unnecessary-dependson&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-unused-existing-resources&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-unused-params&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no-unused-vars&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;outputs-should-not-contain-secrets&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;prefer-interpolation&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;prefer-unquoted-property-names&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;protect-commandtoexecute-secrets&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;secure-parameter-default&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;secure-params-in-nested-deploy&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;secure-secrets-in-params&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;simplify-interpolation&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;simplify-json-null&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-parent-property&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-recent-api-versions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;warning&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;maxAllowedAgeInDays&quot;</span><span class="punctuation">:</span> <span class="number">365</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-recent-module-versions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-resource-id-functions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-resource-symbol-reference&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-safe-access&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-secure-value-for-secure-inputs&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-stable-resource-identifiers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;use-stable-vm-image&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;what-if-short-circuiting&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;level&quot;</span><span class="punctuation">:</span> <span class="string">&quot;off&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>作成したファイルを配置します。</p>
<img src="/images/2025/20250509b/image_3.png" alt="" width="1200" height="421" loading="lazy">

<p>その上で、コマンドを再実行すると表示がフィルタリングされています。</p>
<img src="/images/2025/20250509b/image_4.png" alt="" width="1200" height="158" loading="lazy">

<h2 id="リファクタリング方法">リファクタリング方法</h2><p>警告が出たリソースのコードを確認します。</p>
<p>VSCode用のBicep拡張機能をインストールしている場合はそのリソースで使えるバージョン一覧がインテリセンスとして表示されます。</p>
<p>最新のAPIバージョンが表示された場合は、最新バージョンへの適用を検討します。</p>
<img src="/images/2025/20250509b/image_5.png" alt="" width="1200" height="277" loading="lazy">

<h2 id="さいごに">さいごに</h2><p>以上が最新APIバージョンへの効率的な対応方法のご紹介でした。</p>
<p>APIバージョンが変わることで仕様の変更などが加わる可能性があるため、デプロイテストの実施はお忘れなく。</p>
<p>リンターツールは、コード上でセキュリティ的に問題のある個所を表示するなど、コードの最新化以外にBicepコードのリファクタリングに使えるので便利です。用途に合わせて活用をお勧めします。</p>
]]></content>
    <summary type="html">Azure Bicepは、Microsoft Azureのリソースを効率的に管理するために開発された宣言型の言語です。Azure上のインフラストラクチャをコードとして定義し、デプロイすることを可能にします。Azure Bicepは便利で強力なツールですが...</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Azure" scheme="https://future-architect.github.io/tags/Azure/"/>
    <category term="FutureOne" scheme="https://future-architect.github.io/tags/FutureOne/"/>
    <category term="Linter" scheme="https://future-architect.github.io/tags/Linter/"/>
    <category term="リファクタリング" scheme="https://future-architect.github.io/tags/%E3%83%AA%E3%83%95%E3%82%A1%E3%82%AF%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0/"/>
  </entry>
  <entry>
    <title>Terraform設計ガイドラインを公開しました</title>
    <link href="https://future-architect.github.io/articles/20250409a/"/>
    <id>https://future-architect.github.io/articles/20250409a/</id>
    <published>2025-04-08T15:00:00.000Z</published>
    <updated>2025-04-08T15:00:00.000Z</updated>
    <author><name>伊藤太斉</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250409a/スクリーンショット_2025-04-08_23.57.15.png" alt="" width="1200" height="650">

<p>こんにちは。TIGの伊藤です。</p>
<p>Terraform連載2025の6日目の記事です。</p>
<p>2025年始から、社員の有志でTerraform設計ガイドラインを編集し、先日公開したので公開までの経緯などについて触れていきます。</p>
<h2 id="公開までの経緯">公開までの経緯</h2><p>Future Enterprise Arch Guidelinesとして、これまでにもWebAPI設計ガイドライン、Slack利用ガイドラインなどを公開してきましたが、これらは社内に知見が溜まってきていることをきっかけに、ガイドラインとして整理して公開しています。</p>
<p>Terraformについても、社内の複数プロジェクトで利用されており、それぞれで工夫したこと、ケアしたポイントなどが知見として出てきていることから、社員がリファレンスとすることも含めて編集、公開することになりました。</p>
<h3 id="チームにおけるガイドラインを設けることの難しさ">チームにおけるガイドラインを設けることの難しさ</h3><p>各プロジェクト、チームでは一定のコーディング規約やガイドラインを定めて開発することがしばしばあるかと思います。</p>
<p>それらは過去に経験した資産を流用するケースであったり、はたまた一から作りあげることもあるでしょう。</p>
<p>ただ、その時に発生するのが、プロジェクト、チーム間での差分です。プロジェクトAでは推奨されていた書き方だが、プロジェクトBでは別の書き方を推奨していた、または特に明記がされていないなど粒度がまちまちになり、開発者としては迷い、判断基準が曖昧になってしまいます。</p>
<p>そんなプロジェクト間での差分をより少なくするためでもあり、Terraformの書き方、設計で迷った時に見るドキュメントとしてTerraform設計ガイドラインが出来上がりました。</p>
<h3 id="実際の参考例">実際の参考例</h3><p>ここではいくつか参考にできそうな例を記載しますが、基本的にはTerraformを少しわかる人から、リーダーの方まで見ていただけるような幅広い内容となっています。</p>
<h4 id="①基本的な文法">①基本的な文法</h4><p>例えば、Terraformではコメントの記載方法が行頭に<code>#</code>を書くパターン、行頭に<code>//</code>を書くパターン、コメントアウトしたい部分を<code>/* */</code>で囲むパターンと３種類あります。<br>本ガイドラインでは、理由としてはVSCodeでコメントアウトする場合に利用されるものであったり、公式としても推奨されていることから、<code>#</code>を推奨としています。</p>
<h4 id="②環境を分離する方法">②環境を分離する方法</h4><p>Terraformでは本番、検証といった環境を分離する方法がいくつかありますが、実際にどのレベルで分離することが最適なのかをガイドライン作成の際に検討を重ねてまとめています。<br>本ガイドラインでは、ディレクトリで分離する方法を推奨案としています。</p>
<h4 id="③開発フロー">③開発フロー</h4><p>当社ですでに公開しているGitブランチフロー規約とも関わってくる話ですが、Terraformにおけるブランチ管理に始まり、開発時に整えておきたい開発者側のガバナンスを記載しています。<br>例えば、実際の開発に使うツール、リンターなどが挙げられます。</p>
<h2 id="まとめ">まとめ</h2><p>Terraform設計ガイドラインの公開を報告しました。</p>
<p>TerraformはIaCの中でもマルチクラウドで利用できる点、さらなる機能拡充がされてきており、より使いやすいツールになってきていると考えています。ただ、それによって迷うポイントも増えてきているかと思いますので、そんなときはぜひ本記事で紹介したガイドラインをぜひご一読ください。</p>
]]></content>
    <summary type="html">年始から、社員の有志でTerraform設計ガイドラインを編集し、先日公開したので公開までの経緯などについて触れていきます。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="ガイドライン" scheme="https://future-architect.github.io/tags/%E3%82%AC%E3%82%A4%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3/"/>
  </entry>
  <entry>
    <title>Terraform × BigQuery データ管理：陥りがちな落とし穴と対策５選（サンプルコード付き）</title>
    <link href="https://future-architect.github.io/articles/20250407a/"/>
    <id>https://future-architect.github.io/articles/20250407a/</id>
    <published>2025-04-06T15:00:00.000Z</published>
    <updated>2025-04-06T15:00:00.000Z</updated>
    <author><name>大前七奈</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250407a/terraform_bigquery.png" alt="" width="800" height="441">

<p>Terraform 連載2025の5日目です。</p>
<p>Terraformは、インフラ構築をコードで管理できる強力なツールですが、BigQueryのデータ管理においては、特有の課題に直面することがあります。本記事では、TerraformでBigQueryを扱う際に陥りやすい落とし穴と、データ管理の品質を高めるための対策について、サンプルコードを交えながら解説します。</p>
<p>ちなみに、以下の構成の前提で解説するため、事前にご了承ください。</p>

<img src="/images/2025/20250407a/mermaid-diagram-2025-04-07-100048.png" alt="" width="1000" height="642" loading="lazy">


<h2 id="1-テーブルのライフサイクル管理">1. テーブルのライフサイクル管理</h2><h3 id="課題：-テーブルの意図しない変更によるデータ損失">課題： テーブルの意図しない変更によるデータ損失</h3><p>Terraformは、リソースのライフサイクルをコードで管理できますが、以下のいくつのケースの際に、テーブルを再度自動作成されるため、意図しないデータ損失が発生します。</p>
<ul>
<li>Terraform上のスキーマ変更を適用したとき</li>
<li>Cloud Functionなどでデータを洗い替えされ、再度<code>terraform apply</code>を実行されたとき（BigqueryAPIのJobConfigによって事前に防ぐことができます。詳しくは３．の項で）。</li>
</ul>
<h3 id="対策">対策</h3><p><code>lifecycle</code>メタ引数を利用して、<code>prevent_destroy</code>を設定し、意図しない削除を防止します。</p>
<p>ただし、このタグの関係で、意図したスキーマを変更する際に<code>Instance cannot be destroyed</code>エラーが発生します。そのため、プロジェクトを立ち上げる初期のようにスキーマ変更が頻繁に発生する場合は、おすすめしません。</p>
<p>ある程度で変更頻度が落ち着いた頃、あるいは、データの保持が必要になった頃に導入することがおすすめです。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1xbx32s-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1xbx32s-1" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;google_bigquery_table&quot;</span> <span class="string">&quot;my_table&quot;</span> {
  dataset_id = google_bigquery_dataset.my_dataset.dataset_id
  table_id   = <span class="string">&quot;my_table&quot;</span>
<span class="line"></span>
  lifecycle {
      prevent_destroy = true
  }
  <span class="comment">#その他の設定</span>
}</pre></td></tr></tbody></table></figure></div>

<h2 id="2-スキーマ定義と管理">2. スキーマ定義と管理</h2><h3 id="課題：-スキーマ定義の不整合、変更に伴うデータ移行の複雑さ">課題： スキーマ定義の不整合、変更に伴うデータ移行の複雑さ</h3><p>Terraformで管理する場合、スキーマ定義の不整合や変更に伴うデータ移行が複雑になることがあります。体系的にスキーマを管理する必要があります。</p>
<h3 id="対策-1">対策</h3><p>各スキーマをTerraform内でベタ書きでなく、Terraformの<code>file</code>関数を利用してJSONスキーマを外だしで管理すること。</p>
<p>また、<code>for_each</code>機能を利用して、各テーブルの差分（別々に管理したいプロパティ）のみをパラメータ化し、テーブルの設定に一貫性をもたせます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1xbx32s-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1xbx32s-2" title="コードの折り返しを切り替える"></label><figcaption><span>table_settings.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  table_setting = toset([
    {
      dataset_name       = <span class="string">&quot;my_dataset&quot;</span>
      table_name         = <span class="string">&quot;my_table&quot;</span>
      description        = <span class="string">&quot;テストテーブル&quot;</span>
      <span class="comment"># 別々に管理したいテーブルのパラメータのみ</span>
    },
    <span class="comment"># その他のテーブル</span>
  ])
}</pre></td></tr></tbody></table></figure></div>

<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1xbx32s-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1xbx32s-3" title="コードの折り返しを切り替える"></label><figcaption><span>bigquery_tables.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;google_bigquery_table&quot;</span> <span class="string">&quot;my_table&quot;</span> {
  for_each   = {for k, v in local.table_setting : k.table_name =&gt; v }
  dataset_id = each.value.dataset_name
  table_id   = each.value.table_name
  schema     = &lt;&lt;EOF
  ${file(<span class="string">&quot;<span class="variable">${path.root}</span>/schemas/<span class="variable">${each.value.dataset_name}</span>/<span class="variable">${each.value.table_name}</span>.json&quot;</span>)}
  EOF
  <span class="comment"># その他固定化する項目</span>
}</pre></td></tr></tbody></table></figure></div>

<h2 id="3-スキーマの変更管理-望ましくない変更がないように">3. スキーマの変更管理(望ましくない変更がないように)</h2><h3 id="課題：-データの不整合、品質劣化">課題： データの不整合、品質劣化</h3><p>BigQueryのスキーマは柔軟に変更できますが、その反面、テーブルにインサートするとき、意図しないスキーマ変更が発生します。</p>
<p>一例として、BigqueryAPIで以下のJobConfigで設定したときに、データだけでなく、スキーマまで上書きされる現象が起こります。</p>
<ul>
<li><code>writeDisposition = WRITE_TRUNCATE</code> かつ</li>
<li><code>autodetect = True</code></li>
</ul>
<p>詳しくは以下のリンクを参考しくてださい。</p>
<ul>
<li>python - Google BigQuery WRITE_TRUNCATE erasing all data - Stack Overflow</li>
<li>Job | BigQuery | Google Cloud</li>
</ul>
<p>また、Terraformでカラムの型を定義する際、下記の例で示しているように、Bigqueryでaliasとして定義されたData Typeにもかかわらずに、自動的に右側の型に変換されることがあります。そして再度<code>terraform apply</code>を実行される際に、Terraform側で差分として認識されてしまいます。</p>
<p>‐ <code>INT64</code>でなく、<code>INTEGER</code><br>‐ <code>FLOAT64</code>でなく、<code>FLOAT</code><br>‐ <code>DECIMEL</code>でなく、<code>NUMERIC</code></p>
<h3 id="対策-2">対策</h3><p>Terraformの<code>google_bigquery_table</code>リソースを利用して、データ型、制約、説明などのメタデータを検証したうえで適切に定義します。また、定義可能な型の範囲もプロジェクトごとにしっかり決めておくこともおすすめです。</p>
<figure class="highlight diff"><figcaption><span>my_table.json</span></figcaption><table><tr><td class="code"><pre><span class="line">[</span><br><span class="line">  &#123;</span><br><span class="line">    &quot;name&quot;: &quot;id&quot;,</span><br><span class="line"><span class="deletion">-    &quot;type&quot;: &quot;INT64&quot;,</span></span><br><span class="line"><span class="addition">+    &quot;type&quot;: &quot;INTEGER&quot;,</span></span><br><span class="line">     &quot;description&quot;: &quot;Unique identifier&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  # その他のカラム</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<h2 id="4-パーティショニングとクラスタリング">4. パーティショニングとクラスタリング</h2><h3 id="課題：-パフォーマンス劣化、コスト増大">課題： パフォーマンス劣化、コスト増大</h3><p>BigQueryのパーティショニングとクラスタリングは、クエリのパフォーマンスとコスト効率を向上させる重要な機能ですが、Terraformでの設定を誤ると、パフォーマンス劣化やコスト増大を招く可能性があります。</p>
<h3 id="対策-3">対策</h3><p>クエリのパターンに合わせて、適切なパーティションキーとクラスタリングキーを選択し、パーティション分割の粒度を適切に設定し、不要なスキャンを減らします。</p>
<p>Terraformの<code>time_partitioning</code>と<code>clustering</code>引数を適切に設定し、パフォーマンスとコストを最適化します。順番も、先にパーティションを設定してから、クラスタリング。また、2025年4月時点、パーティションの上限が10000で、おおよそ27年分（10000÷365日）のデータを保存できます。</p>
<p>https://cloud.google.com/bigquery/quotas?hl=ja#partitioned_tables</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1xbx32s-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1xbx32s-4" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;google_bigquery_table&quot;</span> <span class="string">&quot;my_partitioned_table&quot;</span> {
  dataset_id = google_bigquery_dataset.my_dataset.dataset_id
  table_id   = <span class="string">&quot;my_partitioned_table&quot;</span>
<span class="line"></span>
  time_partitioning {
    type = <span class="string">&quot;DAY&quot;</span>
    field = <span class="string">&quot;date&quot;</span>
  }
<span class="line"></span>
  clustering {
    fields = [<span class="string">&quot;name&quot;</span>]
  }
}</pre></td></tr></tbody></table></figure></div>

<h2 id="5-権限管理とセキュリティ">5. 権限管理とセキュリティ</h2><h3 id="課題：-意図しないデータアクセス、セキュリティリスク">課題： 意図しないデータアクセス、セキュリティリスク</h3><p>BigQueryの権限管理は、データセキュリティ・データ活用の面において非常に重要です。とはいえ、必ずしもデータオーナーがコードベースで権限を管理できるわけではありません。Terraformでの初期設定をどこまですればいいか、非常に悩ましいことです。設定を誤ると、意図しないデータアクセスやセキュリティリスクを招く可能性があります。</p>
<h3 id="対策-4">対策</h3><p>データオーナーがコードまでいじれないため、各テーブルに対して、テーブルグループを事前に設けることによって、データ閲覧者をあえてTerraformでのデータ管理から切り離します。</p>
<p>そして、Terraformの中で、<code>google_bigquery_dataset_access</code>または<code>google_bigquery_table_iam</code>、リソースを利用して、最小権限の原則に従って各テーブルグループに対して、対応するテーブルの閲覧権限のみを付与します。</p>
<p>初期設定として、テーブルグループの中で自動的に規定した各データオーナー部門のみを招待し、日頃の運用上、データオーナーがコードやリポジトリーではなく、グループ画面だけでデータ閲覧者の管理を行えるようになります。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1xbx32s-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1xbx32s-5" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;google_bigquery_dataset_access&quot;</span> <span class="string">&quot;my_dataset_viewer&quot;</span> {
  dataset_id = google_bigquery_dataset.my_dataset.dataset_id
<span class="line"></span>
  role = <span class="string">&quot;roles/bigquery.dataViewer&quot;</span>
<span class="line"></span>
  user_by_email = <span class="string">&quot;dataset_id@my-domain.com&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<h2 id="まとめ">まとめ</h2><p>Terraformは、BigQueryのデータ管理を効率化するための強力なツールですが、適切な設計と運用が不可欠です。本記事で紹介した5つのよくある落とし穴とサンプルコードを参考に、Terraformを活用してBigQueryのデータ管理を最適化し、データ品質とセキュリティを向上させましょう！</p>
]]></content>
    <summary type="html">Terraformは、インフラ構築をコードで管理できる強力なツールですが、BigQueryのデータ管理においては、特有の課題に直面することがあります。本記事では、TerraformでBigQueryを扱う際に陥りやすい落とし穴と、データ管理の品質を高めるための対策について、サンプルコードを交えながら解説します。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="BigQuery" scheme="https://future-architect.github.io/tags/BigQuery/"/>
    <category term="GoogleCloud" scheme="https://future-architect.github.io/tags/GoogleCloud/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="データマネジメント" scheme="https://future-architect.github.io/tags/%E3%83%87%E3%83%BC%E3%82%BF%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88/"/>
  </entry>
  <entry>
    <title>Terraform TIPS 集</title>
    <link href="https://future-architect.github.io/articles/20250404a/"/>
    <id>https://future-architect.github.io/articles/20250404a/</id>
    <published>2025-04-03T15:00:00.000Z</published>
    <updated>2025-04-03T15:00:00.000Z</updated>
    <author><name>前原応光</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250404a/terraform_tips.png" alt="" width="800" height="550">

<h2 id="はじめに">はじめに</h2><p>TIG 前原です。Terraform 連載2025の4日目です。</p>
<p>Terraform を利用する中でこんな使い方があるのかってことを少しでも知ってもらえればと思い、TIPS 集を書いています。<br>既にご存知の方も多い内容かもしれないですが、ご了承ください。</p>
<h2 id="TIPS-内容">TIPS 内容</h2><p>今回、ご紹介する内容を以下に記載します。</p>
<ul>
<li>詳細ログ出力の取得方法</li>
<li>プロバイダの確認コマンド</li>
<li>Terraform 実行中にロックがかかった場合</li>
<li>terraform plan -detailed-exitcode を使った差分チェック</li>
<li>リソースのタイアウト時間の調整</li>
<li>Terraform の並列実行</li>
</ul>
<h3 id="詳細ログ出力の取得方法">詳細ログ出力の取得方法</h3><p>Terraform の挙動を細かく調査したいときに環境変数の設定をすることで、詳細なログを確認できます。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> TF_LOG=DEBUG</span><br></pre></td></tr></table></figure>

<p>Terraform apply をした時などにDEBUG レベルの詳細なログを確認できます。<br>また、ログをファイルとして保存したい場合は、以下で可能です。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> TF_LOG=DEBUG</span><br><span class="line"><span class="built_in">export</span> TF_LOG_PATH=./terraform.log</span><br></pre></td></tr></table></figure>

<h4 id="ログレベルによる利用用途について">ログレベルによる利用用途について</h4><p>各ログレベルでの利用用途についてです。</p>
<ul>
<li>DEBUG: Terraform の内部処理を確認</li>
<li>TRACE: プロバイダがクラウドのAPI に対して行ったリクエスト、レスポンスまで確認したい場合</li>
</ul>
<h3 id="プロバイダの確認コマンド">プロバイダの確認コマンド</h3><p>現在、利用しているプロバイダのバージョンや参照元を確認できます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-yxfkgx-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-yxfkgx-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform providers</span></span><br><span class="line"></span><br><span class="line">Providers required by configuration:</span><br><span class="line">.</span><br><span class="line">├── provider[registry.terraform.io/hashicorp/google] 6.23.0</span><br><span class="line">├── provider[registry.terraform.io/hashicorp/archive]</span><br><span class="line">└── provider[registry.terraform.io/hashicorp/google-beta]</span><br><span class="line"></span><br><span class="line">Providers required by state:</span><br><span class="line"></span><br><span class="line">    provider[registry.terraform.io/hashicorp/archive]</span><br><span class="line"></span><br><span class="line">    provider[registry.terraform.io/hashicorp/google]</span><br></pre></td></tr></table></figure></div>

<h3 id="Terraform-実行中にロックがかかった場合">Terraform 実行中にロックがかかった場合</h3><p>Terraform 実行中に異常終了するとバックエンド（S3 やGCS など）のState ファイルが<code>ロック</code>される時があります。<br><code>terraform force-unlock</code>を利用することで、強制的にロックを解除できます。</p>
<p>例えば、以下のように<code>terraform plan</code>を実行しましたが、ロックがかかっているメッセージが出てきます。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-yxfkgx-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-yxfkgx-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">│ Error: Error acquiring the state lock</span><br><span class="line">│</span><br><span class="line">│ Error message: writing <span class="string">&quot;gs://hoge/dev.tflock&quot;</span> failed: googleapi: Error 412: At least one of the</span><br><span class="line">│ pre-conditions you specified did not hold., conditionNotMet</span><br><span class="line">│ Lock Info:</span><br><span class="line">│   ID:        1743715123443466</span><br><span class="line">│   Path:      gs://hoge</span><br><span class="line">│   Operation: OperationTypePlan</span><br><span class="line">│   Who:       hoge</span><br><span class="line">│   Version:   1.10.0</span><br><span class="line">│   Created:   2025-04-03 21:18:43.090403 +0000 UTC</span><br><span class="line">│   Info:</span><br></pre></td></tr></table></figure></div>

<p>エラーで表示された<code>ID</code>を使い、ロックを解除します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">terraform force-unlock &lt;ID&gt;</span><br></pre></td></tr></table></figure>

<p>本番環境などでロック解除を行う際は、他の作業者がTerraform を実行中でないことを必ず確認してください。誤ったロック解除はState ファイル破損や予期せぬトラブルにつながります。</p>
<h3 id="terraform-plan-detailed-exitcode-を使った差分チェック">terraform plan -detailed-exitcode を使った差分チェック</h3><p>Terraform をCI&#x2F;CD 環境で利用しているときに<code>リソースの差分があるかどうか</code>を自動的に判定したいケースがあると思います。<br>その時に役立つオプションとして<code>terraform plan</code>コマンドの<code>-detailed-exitcode</code>です。</p>
<h4 id="detailed-exitcodeとは"><code>-detailed-exitcode</code>とは</h4><p>通常、Terraform コマンドの<code>plan</code>コマンドは、終了コードとして以下を返します。</p>
<ul>
<li>0（成功）: Terraform が正常に処理完了（差分があっても）</li>
<li>1（エラー）: Terraform に何らかのエラーを検知（構文エラーなど）</li>
</ul>
<p>一方で、<code>-detailed-exitcode</code>を使用すると、Terraform の終了コードがより詳細になります。</p>
<ul>
<li>0（差分なし）: Terraform によるリソース更新がない</li>
<li>1（エラー）: Terraform に何らかのエラーを検知（構文エラーなど）</li>
<li>2（差分あり）: Terraform によるリソース差分が発生し、変更が必要なとき</li>
</ul>
<p>実際にターミナルで確認する場合のコマンドは以下です。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform plan -detailed-exitcode</span></span><br><span class="line">echo $? # 終了コード確認</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## 出力例</span></span></span><br><span class="line">2</span><br></pre></td></tr></table></figure>

<p><code>-detailed-exitcode</code>をCI&#x2F;CD のパイプラインに組み込むことで差分発生時のハンドリングが可能となります。<br>以下は GitHub Actions のサンプルで、Terraform Plan 実行後に差分が検出された場合（終了コードが2）のみ、terraform apply を実行する フローです。</p>
<div class="code-block"><figure class="highlight yaml"><input type="checkbox" id="code-wrap-yxfkgx-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-yxfkgx-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Terraform</span> <span class="string">Plan</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">terraform</span> <span class="string">plan</span> <span class="string">-detailed-exitcode</span></span><br><span class="line">  <span class="attr">continue-on-error:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">id:</span> <span class="string">plan</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Terraform</span> <span class="string">Apply</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">steps.plan.outcome</span> <span class="string">==</span> <span class="string">&#x27;failure&#x27;</span> <span class="string">&amp;&amp;</span> <span class="string">steps.plan.exit-code</span> <span class="string">==</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">terraform</span> <span class="string">apply</span> <span class="string">-auto-approve</span></span><br></pre></td></tr></table></figure></div>

<h3 id="リソースのタイムアウト時間の調整">リソースのタイムアウト時間の調整</h3><p>Terraform でリソース作成、削除するときに何かしらの理由で処理が長くなる時があります。<br>そんな時に便利なのがTerraform の<code>timeouts</code>ブロックです。<br>これを利用することで想定以上の時間が経過している時に<code>処理失敗</code>としてエラーにできます。<br>例えば、以下のように設定することで最大40分まで待機し、処理がその時間内に完了しなければエラーとします。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-yxfkgx-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-yxfkgx-4" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;google_sql_database_instance&quot;</span> <span class="string">&quot;example_db&quot;</span> {
  name             = <span class="string">&quot;my-example-db&quot;</span>
  database_version = <span class="string">&quot;POSTGRES_15&quot;</span>
  region           = <span class="string">&quot;asia-northeast1&quot;</span>
<span class="line"></span>
  settings {
    tier = <span class="string">&quot;db-n1-standard-4&quot;</span>
    disk_size = <span class="number">1000</span>
<span class="line"></span>
    backup_configuration {
      enabled = true
    }
  }
<span class="line"></span>
  <span class="comment"># タイムアウトを明示的に調整</span>
  timeouts {
    create = <span class="string">&quot;40m&quot;</span> <span class="comment"># 作成完了まで最大40分待機</span>
    delete = <span class="string">&quot;30m&quot;</span> <span class="comment"># 削除完了まで最大30分待機</span>
    update = <span class="string">&quot;60m&quot;</span> <span class="comment"># 更新完了まで最大60分待機</span>
  }
}</pre></td></tr></tbody></table></figure></div>

<ul>
<li>create: リソースの作成処理を待つ時間の最大値</li>
<li>delete: リソース削除処理を待つ時間の最大値</li>
<li>update: リソースの更新処理を待つ時間の最大値（必要に応じて設定）</li>
</ul>
<h3 id="Terraform-の並列実行">Terraform の並列実行</h3><p>Terraform を使ってリソースを作成、更新などする際、リソース数が多いと適用に時間がかかることがあります。<br>そんな時にTerraform が持つ並列実行のオプション（-parallelism）を活用すると、実行速度の改善が可能です。<br>ユースケースとしては、大量のリソースを持つプロジェクトの初期構築時や、インフラ更新時間を短縮したい時に効果的です。</p>
<p>Terraform は、デフォルトで最大10リソースを並列（同時）に作成、更新します。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## 並列数を30に指定した場合</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform plan --parallelism=30</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## 環境変数でPlan の並列数を指定</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">export</span> TF_CLI_ARGS_plan=<span class="string">&quot;--parallelism=30&quot;</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">## 環境変数でApply の並列数を指定</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">export</span> TF_CLI_ARGS_apply=<span class="string">&quot;--parallelism=30&quot;</span></span></span><br></pre></td></tr></table></figure>

<p>並列数を大きくすると高速になりますが、API のレートリミットに引っかかる可能性があるため、20-30程度から試すのがおすすめです。</p>
<h2 id="さいごに">さいごに</h2><p>様々なTerraform のTIPS を記載しました。</p>
<p>今回ご紹介したTIPSが少しでもお役に立てれば幸いです。</p>
]]></content>
    <summary type="html">Terraform を利用する中でこんな使い方があるのかってことを少しでも知ってもらえればと思い、TIPS 集を書いています。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="Tips" scheme="https://future-architect.github.io/tags/Tips/"/>
  </entry>
  <entry>
    <title>【Terraform】プロビジョニングとはなんぞや？</title>
    <link href="https://future-architect.github.io/articles/20250403a/"/>
    <id>https://future-architect.github.io/articles/20250403a/</id>
    <published>2025-04-02T15:00:00.000Z</published>
    <updated>2025-04-02T15:00:00.000Z</updated>
    <author><name>森友雅</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250403a/16169_color.png" alt="" width="748" height="550">

<p>Terraform連載2025の3日目です。</p>
<h2 id="背景">背景</h2><p>業務でTerrafomでAWSのリソースを作成する作業を担当することになり、作業に取り掛かる前にTerraformとは何であるか調べていました。</p>
<p>公式を確認したところ…</p>
<blockquote>
<p>Use infrastructure as code to automate the provisioning of your infrastructure including servers, databases, firewall policies, and almost any other resource.<br>（Geminiによる和訳）インフラストラクチャをコード化して、サーバー、データベース、ファイアウォールポリシーなど、ほぼすべてのリソースのプロビジョニングを自動化しましょう。</p>
</blockquote>
<p>引用：Terraform by HashiCorp</p>
<p>…とあり、どうやらTerraformを使えば「<strong>IaC（infrastructure as code）を使用してインフラのプロビジョニングを自動化できる</strong>」ようです。私はこのとき「ん？プロビジョニングとはなんぞや？」となり、インフラの何かを自動化できるんだな程度しか理解していませんでした。そんな状態でTerraform関連のインフラのことをいろいろと調べていたのですが、いたるサイトで当然のように「プロビジョニング」が使われていたので「これは知っておかないとまずいな…」と感じました。</p>
<p>以上の背景から、本記事では「プロビジョニング」という用語を理解し、使えるようになるまで深ぼっていこうと思います。</p>
<h2 id="結論">結論</h2><ul>
<li>プロビジョニング ＝ 供給すること</li>
<li>プロビジョニングは広義の意味であるため、狭義の意味の「○○プロビジョニング」が数多く存在</li>
<li>プロビジョニング ≠ 構成管理</li>
</ul>
<h2 id="プロビジョニングの語源">プロビジョニングの語源</h2><p>プロビジョニング（provisioning）とは動詞のプロビジョン（provision）「供給する」の動名詞で、「供給すること」という意味です。<br>(参考：provision - ウィクショナリー日本語版)</p>
<p>IT領域におけるプロビジョニングとは、ハードウェアやネットワーク、仮想マシンやその他リソースなどのITインフラを「供給すること」を指します。ここで注意してほしいのはプロビジョニングという用語はTerraformやAWSなどのクラウド領域に閉じた話ではなく、幅広いインフラ領域で使用されているということです。</p>
<blockquote>
<p>プロビジョニングとは、必要に応じてネットワークやコンピュータの設備などのリソースを提供できるよう予測し、準備しておくことです。供給や設備等の意味を表すプロビジョン（provision）という単語がもととなって派生した言葉です。</p>
</blockquote>
<p>引用：プロビジョニングとは | クラウド・データセンター用語集／IDCフロンティア</p>
<blockquote>
<p>プロビジョニング（provisioning）は、「供給、準備、提供」といった意味を持つ「provision」に由来する言葉。IT分野では、ユーザーの需要を予測し、ネットワークの設備やシステムリソースなどを準備しておくこと。もともとは、通信事業者が回線設備などを準備し、ユーザーにネットワークを提供できるようにすることを指していた。</p>
</blockquote>
<p>引用：IT用語集「プロビジョニング」 | スマートワーク総研</p>
<blockquote>
<p>Provisioning is the process of creating and setting up IT infrastructure, and includes the steps required to manage user and system access to various resources. Provisioning is an early stage in the deployment of servers, applications, network components, storage, edge devices, and more.<br>（Geminiによる和訳）Provisioning（プロビジョニング）は、ITインフラストラクチャの作成と設定のプロセスであり、様々なリソースへのユーザーおよびシステムアクセスを管理するために必要なステップを含みます。プロビジョニングは、サーバー、アプリケーション、ネットワークコンポーネント、ストレージ、エッジデバイスなどの展開における初期段階です。</p>
</blockquote>
<p>引用：What is provisioning?</p>
<blockquote>
<p>In IT and computing, provisioning refers to the process of setting up IT infrastructure, which includes hardware, networks, virtual machines and other resources, and making resources and data available to systems and users.<br>（Geminiによる和訳）ITおよびコンピューティングにおいて、プロビジョニングとは、ハードウェア、ネットワーク、仮想マシン、その他のリソースを含むITインフラストラクチャを設定し、システムやユーザーがリソースやデータを利用できるようにするプロセスのことを指します。</p>
</blockquote>
<p>引用：What is Provisioning? | IBM</p>
<h2 id="プロビジョニングの種類">プロビジョニングの種類</h2><p>前節でも説明したように、IT領域における「プロビジョニング」とは「<strong>ITインフラを供給すること</strong>」であり、広義の意味を持っています。そのため、プロビジョニングにはいくつかの種類が存在します。以下に主要なプロビジョニングの種類を紹介します。</p>
<p>以下の主要なプロビジョニングの種類の説明は「What is provisioning?」と「What is Provisioning? | IBM」を参考にしています。</p>
<h3 id="サーバプロビジョニング（server-provisioning）">サーバプロビジョニング（server provisioning）</h3><p>物理サーバや仮想サーバ（ハイパーバイザなどを使って実現）をセットアップし、オペレーティングシステム（OS）やアプリケーションなどのソフトウェアをインストール・設定し、必要なネットワークやストレージコンポーネントに接続する一連のプロセスを指します。これにより、サーバをビジネス要件に適した状態にします。</p>
<h3 id="ネットワークプロビジョニング（network-provisioning）">ネットワークプロビジョニング（network provisioning）</h3><p>ルータやスイッチ、ファイアウォールなどのネットワークコンポーネントを設定し、IPアドレスを割り当て、ネットワークへのアクセスを管理するプロセスを指します。また、ネットワークの運用状態を確認し、適切なセキュリティ対策を実施することもこのプロセスに含まれます。</p>
<h3 id="ユーザプロビジョニング-user-provisioning">ユーザプロビジョニング(user provisioning)</h3><p>アイデンティティアクセス管理（IAM:identity and access management）の一種で、ユーザアカウントを作成、変更、削除し、アクセス権を管理するプロセスです。このプロセスの例としてロールベースアクセス制御（RBAC:role-based access control）があります。</p>
<h3 id="クラウドプロビジョニング-cloud-provisioning">クラウドプロビジョニング(cloud provisioning)</h3><p>クラウド環境を支えるインフラを構築するプロセスです。このプロセスには、ネットワーク、ストレージ、仮想化などの基盤をセットアップし、クラウド内で利用するリソース、サービス、アプリケーションを構成する作業が含まれます。</p>
<p>プロビジョニングの種類は他にもまだまだあります。</p>
<ul>
<li>アプリケーションプロビジョニング(application provisioning)</li>
<li>デバイスプロビジョニング(device provisioning)</li>
<li>シン・プロビジョニング(thin provisioning)</li>
</ul>
<p>以上のように、プロビジョニングは様々な用途で定義されています。すべての○○プロビジョニングが狭義の意味で定義されているわけではなく、広義の意味で定義されているものもあります。例えばクラウドプロビジョニングは広義寄りで、この中にサーバプロビジョニングやユーザプロビジョニング、ネットワークプロビジョニングなど、数多くのプロビジョニングが含まれています。</p>
<h2 id="プロビジョニング-≠-構成管理-configuration-management-であるということ">プロビジョニング ≠ 構成管理 (configuration management)であるということ</h2><blockquote>
<p>Provisioning is not the same thing as configuration management, but they are both steps in the deployment process. Once a system has been provisioned, the next step is to configure the system and maintain it consistently over time.<br>（Geminiによる和訳）プロビジョニングは構成管理と同じものではありませんが、両方とも展開プロセスにおけるステップです。システムがプロビジョニングされたら、次のステップはシステムを構成し、時間の経過とともに一貫性を維持することです。</p>
</blockquote>
<p>引用：What is provisioning?</p>
<p>「プロビジョニングと構成管理はどちらもデプロイのプロセスではあるが、同じものではない」と説明されています。前述の引用内容にもあるように新しくシステムを導入すのが「プロビジョニング」で、プロビジョニングしたシステムをメンテするのが「構成管理」にあたりそうです。</p>
<p>余談:</p>
<p>Terraformは「プロビジョニングツール」、Ansibleは「構成管理ツール」と認識していたのですが、Terraformでパラメータを調整できますし、Ansibleでシステムの新規追加もできるので、TerraformとAnsibleともに「プロビジョニングツール」であり「構成管理ツール」でもあると感じました。（TerraformとAnsibleの違いの説明は「Ansible vs. Terraform: What’s the difference?」がわかりやすかったです。Terraformでは宣言型で記述し、状態を管理するためのライフサイクルを持っているようです。対してAnsibleでは宣言型と手続き型のハイブリッド(本質的には手続き型)で記述し、ライフサイクルを持たないようです。）</p>
<h2 id="さいごに">さいごに</h2><p>「プロビジョニング」について理解が深まったかなと思ってます。</p>
<p>私は業務でAWS ECSを利用しているのですが、サービスを実行するときに「今タスクをプロビジョニングしてますー」と言ったり、サービスを停止するときに「今デプロビジョニング中ですー」と自信をもって言えるようになったのが本記事を作成して一番良かったなと感じている点です。（笑）</p>
]]></content>
    <summary type="html">業務でTerrafomでAWSのリソースを作成する作業を担当することになり、作業に取り掛かる前にTerraformとは何かを調べていました。公式を確認したところ...</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="Ansible" scheme="https://future-architect.github.io/tags/Ansible/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="初心者向け" scheme="https://future-architect.github.io/tags/%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91/"/>
    <category term="用語解説" scheme="https://future-architect.github.io/tags/%E7%94%A8%E8%AA%9E%E8%A7%A3%E8%AA%AC/"/>
  </entry>
  <entry>
    <title>Terraform実行ユーザー用の最小権限の原則を支援するPike触ってみた</title>
    <link href="https://future-architect.github.io/articles/20250401a/"/>
    <id>https://future-architect.github.io/articles/20250401a/</id>
    <published>2025-03-31T15:00:00.000Z</published>
    <updated>2025-03-31T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>TIG 真野です。Terraform連載2025の2日目です。</p>
<p>Pikeを触ってみた記事です。</p>
<h2 id="Pikeとは">Pikeとは</h2><p>Pike は James Woolfendenさんによって開発されたTerraformのコードを静的解析し、その <code>terraform apply</code> に必要な最小権限の原則に則ったIAMポリシーを生成するツールです。直接 <code>.tf</code> のコードをスキャンするというところが、良さそうと思ったポイントです。</p>
<p>Terraformを用いてインフラ構築する際には、強めの権限（本来は不要であるサービスの作成権限など）を付与して行うことが多いと思います。そのため、万が一のセキュリティ事故や誤操作で思いがけない結果に繋がる懸念がありました。しかし、最小権限の原則を忠実に守ろうとすると難易度・対応コストが高くなるため、ある程度割り切った運用を採用することが多いように思えます（もちろん、開発時は大きめを許容するが、サービスがある程度枯れてきたら権限範囲を小さくするといった運用は今までもよく行われてきたと思います）。</p>
<p>個人的にも、<code>terraform apply</code> に必要な権限を、インフラのリソース種別が追加する事に調査・ポリシーに追加する運用な流石に厳しいなと感じていたので、こうした支援ツールが福音に聞こえました。</p>
<p>さっそく触ってみます。</p>
<h2 id="インストール">インストール</h2><p>README に環境別の手順があります。私はGo言語環境があったので、以下でインストールします。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ksso04-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ksso04-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go install github.com/jameswoolfenden/pike@v0.3.47</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">pike -h</span></span><br><span class="line">NAME:</span><br><span class="line">   pike - Generate IAM policy from your IAC code</span><br><span class="line"></span><br><span class="line">USAGE:</span><br><span class="line">   pike [global options] command [command options]</span><br><span class="line"></span><br><span class="line">VERSION:</span><br><span class="line">   9.9.9</span><br><span class="line"></span><br><span class="line">AUTHOR:</span><br><span class="line">   James Woolfenden &lt;james.woolfenden@gmail.com&gt;</span><br><span class="line"></span><br><span class="line">COMMANDS:</span><br><span class="line">   apply, a    Create a policy and use it to instantiate the IAC</span><br><span class="line">   compare, c  policy comparison of deployed versus IAC</span><br><span class="line">   inspect, x  policy comparison of environment versus IAC</span><br><span class="line">   invoke, i   Triggers a gitHub action specified with the workflow flag</span><br><span class="line">   make, m     make the policy/role required for this IAC to deploy</span><br><span class="line">   parse, p    Triggers a gitHub action specified with the workflow flag</span><br><span class="line">   pull, l     Clones remote repo and scans it using pike</span><br><span class="line">   readme, r   Looks in dir for a README.md and updates it with the Policy required to build the code</span><br><span class="line">   remote, o   Create/Update the Policy and set credentials/secret for Github Action</span><br><span class="line">   scan, s     scan a directory for IAM code</span><br><span class="line">   version, v  Outputs the application version</span><br><span class="line">   watch, w    Waits for policy update</span><br><span class="line">   help, h     Shows a list of commands or help for one command</span><br><span class="line"></span><br><span class="line">GLOBAL OPTIONS:</span><br><span class="line">   --help, -h     show help</span><br><span class="line">   --version, -v  print the version</span><br></pre></td></tr></table></figure></div>

<p>pike経由で直接、IAMポリシーを直接AWS上にデプロイできるなど多くのコマンドがありますが、今回は <code>scan</code> だけ用います。</p>
<h2 id="対象リソースの準備">対象リソースの準備</h2><p>以下のような API Gateway + Lambda + DynamoDB（図にはないですがCloudWatchメトリクスやアラーム）を含んだリソースを持つTerraformコードを用意します。なお、図はinframapで生成したものを簡単に加筆したものです。</p>
<img fetchpriority="high" src="/images/2025/20250401a/{28A12EA6-0F25-4E2C-8FDB-4BDCE8FC54AF}.png" alt="{28A12EA6-0F25-4E2C-8FDB-4BDCE8FC54AF}.png" width="491" height="560">

<details><summary>main.tf</summary>

<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-ksso04-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ksso04-2" title="コードの折り返しを切り替える"></label><figcaption><span>main.tf</span></figcaption><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_providers &#123;</span><br><span class="line">    aws = &#123;</span><br><span class="line">      <span class="built_in">source</span>  = <span class="string">&quot;hashicorp/aws&quot;</span></span><br><span class="line">      version = <span class="string">&quot;~&gt; 5.0&quot;</span> <span class="comment"># バージョンは適宜調整してください</span></span><br><span class="line">    &#125;</span><br><span class="line">    archive = &#123;</span><br><span class="line">      <span class="built_in">source</span>  = <span class="string">&quot;hashicorp/archive&quot;</span></span><br><span class="line">      version = <span class="string">&quot;~&gt; 2.2&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider <span class="string">&quot;aws&quot;</span> &#123;</span><br><span class="line">  region = <span class="string">&quot;ap-northeast-1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">variable <span class="string">&quot;resource_prefix&quot;</span> &#123;</span><br><span class="line">  description = <span class="string">&quot;Prefix for all created resources&quot;</span></span><br><span class="line">  <span class="built_in">type</span>        = string</span><br><span class="line">  default     = <span class="string">&quot;pile-test&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_dynamodb_table&quot;</span> <span class="string">&quot;data_table&quot;</span> &#123;</span><br><span class="line">  name           = <span class="string">&quot;<span class="variable">$&#123;var.resource_prefix&#125;</span>-data-table&quot;</span></span><br><span class="line">  billing_mode   = <span class="string">&quot;PAY_PER_REQUEST&quot;</span> <span class="comment"># オンデマンドキャパシティ</span></span><br><span class="line">  hash_key       = <span class="string">&quot;id&quot;</span></span><br><span class="line"></span><br><span class="line">  attribute &#123;</span><br><span class="line">    name = <span class="string">&quot;id&quot;</span></span><br><span class="line">    <span class="built_in">type</span> = <span class="string">&quot;S&quot;</span> <span class="comment"># String</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_iam_role&quot;</span> <span class="string">&quot;lambda_exec_role&quot;</span> &#123;</span><br><span class="line">  name = <span class="string">&quot;<span class="variable">$&#123;var.resource_prefix&#125;</span>-lambda-exec-role&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># Lambdaサービスがこのロールを引き受けることを許可</span></span><br><span class="line">  assume_role_policy = jsonencode(&#123;</span><br><span class="line">    Version = <span class="string">&quot;2012-10-17&quot;</span></span><br><span class="line">    Statement = [&#123;</span><br><span class="line">      Action = <span class="string">&quot;sts:AssumeRole&quot;</span></span><br><span class="line">      Effect = <span class="string">&quot;Allow&quot;</span></span><br><span class="line">      Principal = &#123;</span><br><span class="line">        Service = <span class="string">&quot;lambda.amazonaws.com&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Lambda用IAMポリシー (CloudWatch Logs &amp; DynamoDBアクセス)</span></span><br><span class="line">resource <span class="string">&quot;aws_iam_policy&quot;</span> <span class="string">&quot;lambda_policy&quot;</span> &#123;</span><br><span class="line">  name        = <span class="string">&quot;<span class="variable">$&#123;var.resource_prefix&#125;</span>-lambda-policy&quot;</span></span><br><span class="line">  description = <span class="string">&quot;Policy for Lambda to access CloudWatch Logs and DynamoDB table&quot;</span></span><br><span class="line"></span><br><span class="line">  policy = jsonencode(&#123;</span><br><span class="line">    Version = <span class="string">&quot;2012-10-17&quot;</span></span><br><span class="line">    Statement = [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment"># CloudWatch Logsへの書き込み権限</span></span><br><span class="line">        Action = [</span><br><span class="line">          <span class="string">&quot;logs:CreateLogGroup&quot;</span>, <span class="comment"># Lambda初回実行時に必要</span></span><br><span class="line">          <span class="string">&quot;logs:CreateLogStream&quot;</span>,</span><br><span class="line">          <span class="string">&quot;logs:PutLogEvents&quot;</span></span><br><span class="line">        ]</span><br><span class="line">        Effect   = <span class="string">&quot;Allow&quot;</span></span><br><span class="line">        Resource = <span class="string">&quot;arn:aws:logs:ap-northeast-1:<span class="variable">$&#123;data.aws_caller_identity.current.account_id&#125;</span>:log-group:/aws/lambda/<span class="variable">$&#123;var.resource_prefix&#125;</span>-function:*&quot;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment"># DynamoDBテーブルへの基本的なCRUD操作権限</span></span><br><span class="line">        Action = [</span><br><span class="line">          <span class="string">&quot;dynamodb:PutItem&quot;</span>,</span><br><span class="line">          <span class="string">&quot;dynamodb:GetItem&quot;</span>,</span><br><span class="line">          <span class="string">&quot;dynamodb:UpdateItem&quot;</span>,</span><br><span class="line">          <span class="string">&quot;dynamodb:DeleteItem&quot;</span>,</span><br><span class="line">          <span class="string">&quot;dynamodb:Query&quot;</span>, <span class="comment"># 必要に応じて追加</span></span><br><span class="line">          <span class="string">&quot;dynamodb:Scan&quot;</span>   <span class="comment"># 必要に応じて追加 (最小権限的には非推奨な場合が多い)</span></span><br><span class="line">        ]</span><br><span class="line">        Effect   = <span class="string">&quot;Allow&quot;</span></span><br><span class="line">        <span class="comment"># 作成した特定のDynamoDBテーブルARNを指定</span></span><br><span class="line">        Resource = aws_dynamodb_table.data_table.arn</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># ロールにポリシーをアタッチ</span></span><br><span class="line">resource <span class="string">&quot;aws_iam_role_policy_attachment&quot;</span> <span class="string">&quot;lambda_attach&quot;</span> &#123;</span><br><span class="line">  role       = aws_iam_role.lambda_exec_role.name</span><br><span class="line">  policy_arn = aws_iam_policy.lambda_policy.arn</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 現在のアカウントIDを取得するために使用</span></span><br><span class="line">data <span class="string">&quot;aws_caller_identity&quot;</span> <span class="string">&quot;current&quot;</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line">data <span class="string">&quot;archive_file&quot;</span> <span class="string">&quot;lambda_zip&quot;</span> &#123;</span><br><span class="line">  <span class="built_in">type</span>        = <span class="string">&quot;zip&quot;</span></span><br><span class="line">  output_path = <span class="string">&quot;<span class="variable">$&#123;path.module&#125;</span>/<span class="variable">$&#123;var.resource_prefix&#125;</span>-lambda.zip&quot;</span></span><br><span class="line">  <span class="built_in">source</span> &#123;</span><br><span class="line">    content = &lt;&lt;-<span class="string">EOT</span></span><br><span class="line"><span class="string">import json</span></span><br><span class="line"><span class="string">import os</span></span><br><span class="line"><span class="string">import boto3</span></span><br><span class="line"><span class="string">import uuid</span></span><br><span class="line"><span class="string">import logging</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">logger = logging.getLogger()</span></span><br><span class="line"><span class="string">logger.setLevel(logging.INFO)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">dynamodb = boto3.resource(&#x27;dynamodb&#x27;)</span></span><br><span class="line"><span class="string">table_name = os.environ.get(&#x27;DYNAMODB_TABLE&#x27;)</span></span><br><span class="line"><span class="string">table = dynamodb.Table(table_name)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def lambda_handler(event, context):</span></span><br><span class="line"><span class="string">    logger.info(f&quot;Received event: &#123;json.dumps(event)&#125;&quot;)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    try:</span></span><br><span class="line"><span class="string">        # API Gateway v2 HTTP API (payload format v2.0)</span></span><br><span class="line"><span class="string">        if &#x27;body&#x27; in event:</span></span><br><span class="line"><span class="string">            body = json.loads(event.get(&#x27;body&#x27;, &#x27;&#123;&#125;&#x27;))</span></span><br><span class="line"><span class="string">        else: # Direct invocation or other event sources</span></span><br><span class="line"><span class="string">             body = event</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        item_id = body.get(&#x27;id&#x27;, str(uuid.uuid4())) # IDがなければ生成</span></span><br><span class="line"><span class="string">        content = body.get(&#x27;content&#x27;, &#x27;Default content&#x27;)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        response = table.put_item(</span></span><br><span class="line"><span class="string">            Item=&#123;</span></span><br><span class="line"><span class="string">                &#x27;id&#x27;: item_id,</span></span><br><span class="line"><span class="string">                &#x27;content&#x27;: content</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">        )</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        logger.info(f&quot;Successfully put item: &#123;item_id&#125;&quot;)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        return &#123;</span></span><br><span class="line"><span class="string">            &#x27;statusCode&#x27;: 200,</span></span><br><span class="line"><span class="string">            &#x27;headers&#x27;: &#123; &#x27;Content-Type&#x27;: &#x27;application/json&#x27; &#125;,</span></span><br><span class="line"><span class="string">            &#x27;body&#x27;: json.dumps(&#123;</span></span><br><span class="line"><span class="string">                &#x27;message&#x27;: &#x27;Item processed successfully!&#x27;,</span></span><br><span class="line"><span class="string">                &#x27;itemId&#x27;: item_id,</span></span><br><span class="line"><span class="string">                &#x27;dynamoResponse&#x27;: response</span></span><br><span class="line"><span class="string">            &#125;)</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">    except Exception as e:</span></span><br><span class="line"><span class="string">        logger.error(f&quot;Error processing request: &#123;str(e)&#125;&quot;)</span></span><br><span class="line"><span class="string">        return &#123;</span></span><br><span class="line"><span class="string">            &#x27;statusCode&#x27;: 500,</span></span><br><span class="line"><span class="string">            &#x27;headers&#x27;: &#123; &#x27;Content-Type&#x27;: &#x27;application/json&#x27; &#125;,</span></span><br><span class="line"><span class="string">            &#x27;body&#x27;: json.dumps(&#123;&#x27;error&#x27;: str(e)&#125;)</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">EOT</span></span><br><span class="line">    filename = <span class="string">&quot;lambda_function.py&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Lambda関数リソース</span></span><br><span class="line">resource <span class="string">&quot;aws_lambda_function&quot;</span> <span class="string">&quot;api_handler&quot;</span> &#123;</span><br><span class="line">  function_name    = <span class="string">&quot;<span class="variable">$&#123;var.resource_prefix&#125;</span>-function&quot;</span></span><br><span class="line">  handler          = <span class="string">&quot;lambda_function.lambda_handler&quot;</span></span><br><span class="line">  runtime          = <span class="string">&quot;python3.11&quot;</span> <span class="comment"># 必要に応じて変更</span></span><br><span class="line">  role             = aws_iam_role.lambda_exec_role.arn</span><br><span class="line">  filename         = data.archive_file.lambda_zip.output_path</span><br><span class="line">  source_code_hash = data.archive_file.lambda_zip.output_base64sha256</span><br><span class="line"></span><br><span class="line">  <span class="comment"># DynamoDBテーブル名を環境変数として渡す</span></span><br><span class="line">  environment &#123;</span><br><span class="line">    variables = &#123;</span><br><span class="line">      DYNAMODB_TABLE = aws_dynamodb_table.data_table.name</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment"># CloudWatch Logs グループ名は自動生成される (/aws/lambda/&lt;function_name&gt;)</span></span><br><span class="line">  <span class="comment"># ログの保持期間などを設定したい場合は aws_cloudwatch_log_group を別途定義</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># ファイルが変更されたら再デプロイするために必要</span></span><br><span class="line">  depends_on = [</span><br><span class="line">    data.archive_file.lambda_zip,</span><br><span class="line">    aws_iam_role_policy_attachment.lambda_attach,</span><br><span class="line">    aws_dynamodb_table.data_table</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_apigatewayv2_api&quot;</span> <span class="string">&quot;http_api&quot;</span> &#123;</span><br><span class="line">  name          = <span class="string">&quot;<span class="variable">$&#123;var.resource_prefix&#125;</span>-http-api&quot;</span></span><br><span class="line">  protocol_type = <span class="string">&quot;HTTP&quot;</span></span><br><span class="line">  target        = aws_lambda_function.api_handler.invoke_arn <span class="comment"># デフォルトルート用</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Lambda統合</span></span><br><span class="line">resource <span class="string">&quot;aws_apigatewayv2_integration&quot;</span> <span class="string">&quot;lambda_integration&quot;</span> &#123;</span><br><span class="line">  api_id           = aws_apigatewayv2_api.http_api.id</span><br><span class="line">  integration_type = <span class="string">&quot;AWS_PROXY&quot;</span> <span class="comment"># Lambdaプロキシ統合</span></span><br><span class="line">  integration_uri  = aws_lambda_function.api_handler.invoke_arn</span><br><span class="line">  payload_format_version = <span class="string">&quot;2.0&quot;</span> <span class="comment"># Lambda関数のevent形式</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># ルート設定 (例: POST /items)</span></span><br><span class="line">resource <span class="string">&quot;aws_apigatewayv2_route&quot;</span> <span class="string">&quot;post_items&quot;</span> &#123;</span><br><span class="line">  api_id    = aws_apigatewayv2_api.http_api.id</span><br><span class="line">  route_key = <span class="string">&quot;POST /items&quot;</span> <span class="comment"># HTTPメソッドとパス</span></span><br><span class="line">  target    = <span class="string">&quot;integrations/<span class="variable">$&#123;aws_apigatewayv2_integration.lambda_integration.id&#125;</span>&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># デフォルトステージ ($default) - 自動デプロイ有効</span></span><br><span class="line">resource <span class="string">&quot;aws_apigatewayv2_stage&quot;</span> <span class="string">&quot;default_stage&quot;</span> &#123;</span><br><span class="line">  api_id      = aws_apigatewayv2_api.http_api.id</span><br><span class="line">  name        = <span class="string">&quot;default&quot;</span> <span class="comment"># デフォルトステージ名</span></span><br><span class="line">  auto_deploy = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># API GatewayがLambdaを呼び出すための権限</span></span><br><span class="line">resource <span class="string">&quot;aws_lambda_permission&quot;</span> <span class="string">&quot;api_gw_invoke&quot;</span> &#123;</span><br><span class="line">  statement_id  = <span class="string">&quot;AllowAPIGatewayInvoke&quot;</span></span><br><span class="line">  action        = <span class="string">&quot;lambda:InvokeFunction&quot;</span></span><br><span class="line">  function_name = aws_lambda_function.api_handler.function_name</span><br><span class="line">  principal     = <span class="string">&quot;apigateway.amazonaws.com&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 呼び出し元API Gatewayを特定するARN</span></span><br><span class="line">  source_arn = <span class="string">&quot;<span class="variable">$&#123;aws_apigatewayv2_api.http_api.execution_arn&#125;</span>/*/*&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_cloudwatch_metric_alarm&quot;</span> <span class="string">&quot;lambda_error_alarm&quot;</span> &#123;</span><br><span class="line">  alarm_name          = <span class="string">&quot;<span class="variable">$&#123;var.resource_prefix&#125;</span>-lambda-errors&quot;</span></span><br><span class="line">  comparison_operator = <span class="string">&quot;GreaterThanThreshold&quot;</span></span><br><span class="line">  evaluation_periods  = <span class="string">&quot;1&quot;</span> <span class="comment"># 1期間で評価</span></span><br><span class="line">  metric_name         = <span class="string">&quot;Errors&quot;</span></span><br><span class="line">  namespace           = <span class="string">&quot;AWS/Lambda&quot;</span></span><br><span class="line">  period              = <span class="string">&quot;60&quot;</span> <span class="comment"># 60秒</span></span><br><span class="line">  statistic           = <span class="string">&quot;Sum&quot;</span></span><br><span class="line">  threshold           = <span class="string">&quot;0&quot;</span> <span class="comment"># エラーが0より大きい場合 (つまり1回でも発生したら)</span></span><br><span class="line">  alarm_description   = <span class="string">&quot;Alarm when <span class="variable">$&#123;var.resource_prefix&#125;</span>-function lambda function has errors&quot;</span></span><br><span class="line"></span><br><span class="line">  dimensions = &#123;</span><br><span class="line">    FunctionName = aws_lambda_function.api_handler.function_name</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">output <span class="string">&quot;api_endpoint&quot;</span> &#123;</span><br><span class="line">  description = <span class="string">&quot;The invoke URL for the API Gateway stage&quot;</span></span><br><span class="line">  value       = aws_apigatewayv2_stage.default_stage.invoke_url</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">output <span class="string">&quot;lambda_function_name&quot;</span> &#123;</span><br><span class="line">  description = <span class="string">&quot;Name of the created Lambda function&quot;</span></span><br><span class="line">  value       = aws_lambda_function.api_handler.function_name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">output <span class="string">&quot;dynamodb_table_name&quot;</span> &#123;</span><br><span class="line">  description = <span class="string">&quot;Name of the created DynamoDB table&quot;</span></span><br><span class="line">  value       = aws_dynamodb_table.data_table.name</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

</details>

<h2 id="Pikeの実行">Pikeの実行</h2><p>同一ディレクトリ上で、pikeコマンドを実行します。応答速度は一瞬でした。早い。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ksso04-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ksso04-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">pike scan -output json</span></span><br><span class="line">&#123;</span><br><span class="line">    &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">    &quot;Statement&quot;: [</span><br><span class="line">        &#123;</span><br><span class="line">            &quot;Sid&quot;: &quot;VisualEditor0&quot;,</span><br><span class="line">            &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">            &quot;Action&quot;: [</span><br><span class="line">                &quot;apigateway:DELETE&quot;,</span><br><span class="line">                &quot;apigateway:GET&quot;,</span><br><span class="line">                &quot;apigateway:PATCH&quot;,</span><br><span class="line">                &quot;apigateway:POST&quot;,</span><br><span class="line">                &quot;apigateway:PUT&quot;</span><br><span class="line">            ],</span><br><span class="line">            &quot;Resource&quot;: [</span><br><span class="line">                &quot;*&quot;</span><br><span class="line">            ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            &quot;Sid&quot;: &quot;VisualEditor1&quot;,</span><br><span class="line">            &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">            &quot;Action&quot;: [</span><br><span class="line">                &quot;cloudwatch:DeleteAlarms&quot;,</span><br><span class="line">                &quot;cloudwatch:DescribeAlarms&quot;,</span><br><span class="line">                &quot;cloudwatch:ListTagsForResource&quot;,</span><br><span class="line">                &quot;cloudwatch:PutMetricAlarm&quot;</span><br><span class="line">            ],</span><br><span class="line">            &quot;Resource&quot;: [</span><br><span class="line">                &quot;*&quot;</span><br><span class="line">            ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            &quot;Sid&quot;: &quot;VisualEditor2&quot;,</span><br><span class="line">            &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">            &quot;Action&quot;: [</span><br><span class="line">                &quot;dynamodb:CreateTable&quot;,</span><br><span class="line">                &quot;dynamodb:DeleteTable&quot;,</span><br><span class="line">                &quot;dynamodb:DescribeContinuousBackups&quot;,</span><br><span class="line">                &quot;dynamodb:DescribeTable&quot;,</span><br><span class="line">                &quot;dynamodb:DescribeTimeToLive&quot;,</span><br><span class="line">                &quot;dynamodb:ListTagsOfResource&quot;,</span><br><span class="line">                &quot;dynamodb:UpdateTable&quot;,</span><br><span class="line">                &quot;dynamodb:UpdateTimeToLive&quot;</span><br><span class="line">            ],</span><br><span class="line">            &quot;Resource&quot;: [</span><br><span class="line">                &quot;*&quot;</span><br><span class="line">            ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            &quot;Sid&quot;: &quot;VisualEditor3&quot;,</span><br><span class="line">            &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">            &quot;Action&quot;: [</span><br><span class="line">                &quot;ec2:DescribeAccountAttributes&quot;,</span><br><span class="line">                &quot;ec2:DescribeNetworkInterfaces&quot;</span><br><span class="line">            ],</span><br><span class="line">            &quot;Resource&quot;: [</span><br><span class="line">                &quot;*&quot;</span><br><span class="line">            ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            &quot;Sid&quot;: &quot;VisualEditor4&quot;,</span><br><span class="line">            &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">            &quot;Action&quot;: [</span><br><span class="line">                &quot;iam:AttachRolePolicy&quot;,</span><br><span class="line">                &quot;iam:CreatePolicy&quot;,</span><br><span class="line">                &quot;iam:CreateRole&quot;,</span><br><span class="line">                &quot;iam:DeletePolicy&quot;,</span><br><span class="line">                &quot;iam:DeleteRole&quot;,</span><br><span class="line">                &quot;iam:DetachRolePolicy&quot;,</span><br><span class="line">                &quot;iam:GetPolicy&quot;,</span><br><span class="line">                &quot;iam:GetPolicyVersion&quot;,</span><br><span class="line">                &quot;iam:GetRole&quot;,</span><br><span class="line">                &quot;iam:ListAttachedRolePolicies&quot;,</span><br><span class="line">                &quot;iam:ListInstanceProfilesForRole&quot;,</span><br><span class="line">                &quot;iam:ListPolicyVersions&quot;,</span><br><span class="line">                &quot;iam:ListRolePolicies&quot;,</span><br><span class="line">                &quot;iam:PassRole&quot;</span><br><span class="line">            ],</span><br><span class="line">            &quot;Resource&quot;: [</span><br><span class="line">                &quot;*&quot;</span><br><span class="line">            ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            &quot;Sid&quot;: &quot;VisualEditor5&quot;,</span><br><span class="line">            &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">            &quot;Action&quot;: [</span><br><span class="line">                &quot;lambda:AddPermission&quot;,</span><br><span class="line">                &quot;lambda:CreateFunction&quot;,</span><br><span class="line">                &quot;lambda:DeleteFunction&quot;,</span><br><span class="line">                &quot;lambda:GetFunction&quot;,</span><br><span class="line">                &quot;lambda:GetFunctionCodeSigningConfig&quot;,</span><br><span class="line">                &quot;lambda:GetPolicy&quot;,</span><br><span class="line">                &quot;lambda:ListVersionsByFunction&quot;,</span><br><span class="line">                &quot;lambda:RemovePermission&quot;</span><br><span class="line">            ],</span><br><span class="line">            &quot;Resource&quot;: [</span><br><span class="line">                &quot;*&quot;</span><br><span class="line">            ]</span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>権限の概要は以下です。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 サービス / アクセスレベル / リソース / リクエストの条件"><table>
<thead>
<tr>
<th>サービス</th>
<th>アクセスレベル</th>
<th>リソース</th>
<th>リクエストの条件</th>
</tr>
</thead>
<tbody><tr>
<td>API Gateway</td>
<td>フル: 読み取り, 書き込み</td>
<td>すべてのリソース</td>
<td>None</td>
</tr>
<tr>
<td>API Gateway V2</td>
<td>フルアクセス</td>
<td>すべてのリソース</td>
<td>None</td>
</tr>
<tr>
<td>CloudWatch</td>
<td>制限あり: リスト, 読み取り, 書き込み</td>
<td>すべてのリソース</td>
<td>None</td>
</tr>
<tr>
<td>DynamoDB</td>
<td>制限あり: 読み取り, 書き込み</td>
<td>すべてのリソース</td>
<td>None</td>
</tr>
<tr>
<td>EC2</td>
<td>制限あり: リスト</td>
<td>すべてのリソース</td>
<td>None</td>
</tr>
<tr>
<td>IAM</td>
<td>制限あり: リスト, 許可の管理, 読み取り,   書き込み</td>
<td>すべてのリソース</td>
<td>None</td>
</tr>
<tr>
<td>Lambda</td>
<td>制限あり: リスト, 許可の管理, 読み取り,   書き込み</td>
<td>すべてのリソース</td>
<td>None</td>
</tr>
</tbody></table></div>
<p>一見すると大きな分類レベルの違和感がないものの、細かい点が気になります。Pikeとしては基本的にリソースは全て <code>*</code> になるようです。</p>
<p>早速、このJSONを用いてIAMポリシーを作成 ⇛ それをアタッチしたIAMロール作成 ⇛ スイッチロールして利用 ⇛ <code>terraform apply</code> します。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ksso04-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ksso04-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform apply</span></span><br><span class="line">（...中略...）</span><br><span class="line"></span><br><span class="line">Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:</span><br><span class="line">  + create</span><br><span class="line"></span><br><span class="line">Terraform will perform the following actions:</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_apigatewayv2_api.http_api will be created</span></span><br><span class="line">  + resource &quot;aws_apigatewayv2_api&quot; &quot;http_api&quot; &#123;</span><br><span class="line">      + api_endpoint                 = (known after apply)</span><br><span class="line">      + api_key_selection_expression = &quot;$request.header.x-api-key&quot;</span><br><span class="line">      + arn                          = (known after apply)</span><br><span class="line">      + execution_arn                = (known after apply)</span><br><span class="line">      + id                           = (known after apply)</span><br><span class="line">      + name                         = &quot;pile-test-http-api&quot;</span><br><span class="line">      + protocol_type                = &quot;HTTP&quot;</span><br><span class="line">      + route_selection_expression   = &quot;$request.method $request.path&quot;</span><br><span class="line">      + tags_all                     = (known after apply)</span><br><span class="line">      + target                       = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_apigatewayv2_integration.lambda_integration will be created</span></span><br><span class="line">  + resource &quot;aws_apigatewayv2_integration&quot; &quot;lambda_integration&quot; &#123;</span><br><span class="line">      + api_id                                    = (known after apply)</span><br><span class="line">      + connection_type                           = &quot;INTERNET&quot;</span><br><span class="line">      + id                                        = (known after apply)</span><br><span class="line">      + integration_response_selection_expression = (known after apply)</span><br><span class="line">      + integration_type                          = &quot;AWS_PROXY&quot;</span><br><span class="line">      + integration_uri                           = (known after apply)</span><br><span class="line">      + payload_format_version                    = &quot;2.0&quot;</span><br><span class="line">      + timeout_milliseconds                      = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_apigatewayv2_route.post_items will be created</span></span><br><span class="line">  + resource &quot;aws_apigatewayv2_route&quot; &quot;post_items&quot; &#123;</span><br><span class="line">      + api_id             = (known after apply)</span><br><span class="line">      + api_key_required   = false</span><br><span class="line">      + authorization_type = &quot;NONE&quot;</span><br><span class="line">      + id                 = (known after apply)</span><br><span class="line">      + route_key          = &quot;POST /items&quot;</span><br><span class="line">      + target             = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_apigatewayv2_stage.default_stage will be created</span></span><br><span class="line">  + resource &quot;aws_apigatewayv2_stage&quot; &quot;default_stage&quot; &#123;</span><br><span class="line">      + api_id        = (known after apply)</span><br><span class="line">      + arn           = (known after apply)</span><br><span class="line">      + auto_deploy   = true</span><br><span class="line">      + deployment_id = (known after apply)</span><br><span class="line">      + execution_arn = (known after apply)</span><br><span class="line">      + id            = (known after apply)</span><br><span class="line">      + invoke_url    = (known after apply)</span><br><span class="line">      + name          = &quot;default&quot;</span><br><span class="line">      + tags_all      = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_cloudwatch_metric_alarm.lambda_error_alarm will be created</span></span><br><span class="line">  + resource &quot;aws_cloudwatch_metric_alarm&quot; &quot;lambda_error_alarm&quot; &#123;</span><br><span class="line">      + actions_enabled                       = true</span><br><span class="line">      + alarm_description                     = &quot;Alarm when pile-test-function lambda function has errors&quot;</span><br><span class="line">      + alarm_name                            = &quot;pile-test-lambda-errors&quot;</span><br><span class="line">      + arn                                   = (known after apply)</span><br><span class="line">      + comparison_operator                   = &quot;GreaterThanThreshold&quot;</span><br><span class="line">      + dimensions                            = &#123;</span><br><span class="line">          + &quot;FunctionName&quot; = &quot;pile-test-function&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + evaluate_low_sample_count_percentiles = (known after apply)</span><br><span class="line">      + evaluation_periods                    = 1</span><br><span class="line">      + id                                    = (known after apply)</span><br><span class="line">      + metric_name                           = &quot;Errors&quot;</span><br><span class="line">      + namespace                             = &quot;AWS/Lambda&quot;</span><br><span class="line">      + period                                = 60</span><br><span class="line">      + statistic                             = &quot;Sum&quot;</span><br><span class="line">      + tags_all                              = (known after apply)</span><br><span class="line">      + threshold                             = 0</span><br><span class="line">      + treat_missing_data                    = &quot;missing&quot;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_dynamodb_table.data_table will be created</span></span><br><span class="line">  + resource &quot;aws_dynamodb_table&quot; &quot;data_table&quot; &#123;</span><br><span class="line">      + arn              = (known after apply)</span><br><span class="line">      + billing_mode     = &quot;PAY_PER_REQUEST&quot;</span><br><span class="line">      + hash_key         = &quot;id&quot;</span><br><span class="line">      + id               = (known after apply)</span><br><span class="line">      + name             = &quot;pile-test-data-table&quot;</span><br><span class="line">      + read_capacity    = (known after apply)</span><br><span class="line">      + stream_arn       = (known after apply)</span><br><span class="line">      + stream_label     = (known after apply)</span><br><span class="line">      + stream_view_type = (known after apply)</span><br><span class="line">      + tags_all         = (known after apply)</span><br><span class="line">      + write_capacity   = (known after apply)</span><br><span class="line"></span><br><span class="line">      + attribute &#123;</span><br><span class="line">          + name = &quot;id&quot;</span><br><span class="line">          + type = &quot;S&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_iam_policy.lambda_policy will be created</span></span><br><span class="line">  + resource &quot;aws_iam_policy&quot; &quot;lambda_policy&quot; &#123;</span><br><span class="line">      + arn              = (known after apply)</span><br><span class="line">      + attachment_count = (known after apply)</span><br><span class="line">      + description      = &quot;Policy for Lambda to access CloudWatch Logs and DynamoDB table&quot;</span><br><span class="line">      + id               = (known after apply)</span><br><span class="line">      + name             = &quot;pile-test-lambda-policy&quot;</span><br><span class="line">      + name_prefix      = (known after apply)</span><br><span class="line">      + path             = &quot;/&quot;</span><br><span class="line">      + policy           = (known after apply)</span><br><span class="line">      + policy_id        = (known after apply)</span><br><span class="line">      + tags_all         = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_iam_role.lambda_exec_role will be created</span></span><br><span class="line">  + resource &quot;aws_iam_role&quot; &quot;lambda_exec_role&quot; &#123;</span><br><span class="line">      + arn                   = (known after apply)</span><br><span class="line">      + assume_role_policy    = jsonencode(</span><br><span class="line">            &#123;</span><br><span class="line">              + Statement = [</span><br><span class="line">                  + &#123;</span><br><span class="line">                      + Action    = &quot;sts:AssumeRole&quot;</span><br><span class="line">                      + Effect    = &quot;Allow&quot;</span><br><span class="line">                      + Principal = &#123;</span><br><span class="line">                          + Service = &quot;lambda.amazonaws.com&quot;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;,</span><br><span class="line">                ]</span><br><span class="line">              + Version   = &quot;2012-10-17&quot;</span><br><span class="line">            &#125;</span><br><span class="line">        )</span><br><span class="line">      + create_date           = (known after apply)</span><br><span class="line">      + force_detach_policies = false</span><br><span class="line">      + id                    = (known after apply)</span><br><span class="line">      + managed_policy_arns   = (known after apply)</span><br><span class="line">      + max_session_duration  = 3600</span><br><span class="line">      + name                  = &quot;pile-test-lambda-exec-role&quot;</span><br><span class="line">      + name_prefix           = (known after apply)</span><br><span class="line">      + path                  = &quot;/&quot;</span><br><span class="line">      + tags_all              = (known after apply)</span><br><span class="line">      + unique_id             = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_iam_role_policy_attachment.lambda_attach will be created</span></span><br><span class="line">  + resource &quot;aws_iam_role_policy_attachment&quot; &quot;lambda_attach&quot; &#123;</span><br><span class="line">      + id         = (known after apply)</span><br><span class="line">      + policy_arn = (known after apply)</span><br><span class="line">      + role       = &quot;pile-test-lambda-exec-role&quot;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_lambda_function.api_handler will be created</span></span><br><span class="line">  + resource &quot;aws_lambda_function&quot; &quot;api_handler&quot; &#123;</span><br><span class="line">      + architectures                  = (known after apply)</span><br><span class="line">      + arn                            = (known after apply)</span><br><span class="line">      + code_sha256                    = (known after apply)</span><br><span class="line">      + filename                       = &quot;./pile-test-lambda.zip&quot;</span><br><span class="line">      + function_name                  = &quot;pile-test-function&quot;</span><br><span class="line">      + handler                        = &quot;lambda_function.lambda_handler&quot;</span><br><span class="line">      + id                             = (known after apply)</span><br><span class="line">      + invoke_arn                     = (known after apply)</span><br><span class="line">      + last_modified                  = (known after apply)</span><br><span class="line">      + memory_size                    = 128</span><br><span class="line">      + package_type                   = &quot;Zip&quot;</span><br><span class="line">      + publish                        = false</span><br><span class="line">      + qualified_arn                  = (known after apply)</span><br><span class="line">      + qualified_invoke_arn           = (known after apply)</span><br><span class="line">      + reserved_concurrent_executions = -1</span><br><span class="line">      + role                           = (known after apply)</span><br><span class="line">      + runtime                        = &quot;python3.11&quot;</span><br><span class="line">      + signing_job_arn                = (known after apply)</span><br><span class="line">      + signing_profile_version_arn    = (known after apply)</span><br><span class="line">      + skip_destroy                   = false</span><br><span class="line">      + source_code_hash               = &quot;HDyCH2iLAujNxtRIhBNof46Cgb8qWglR5YUSMov/PNg=&quot;</span><br><span class="line">      + source_code_size               = (known after apply)</span><br><span class="line">      + tags_all                       = (known after apply)</span><br><span class="line">      + timeout                        = 3</span><br><span class="line">      + version                        = (known after apply)</span><br><span class="line"></span><br><span class="line">      + environment &#123;</span><br><span class="line">          + variables = &#123;</span><br><span class="line">              + &quot;DYNAMODB_TABLE&quot; = &quot;pile-test-data-table&quot;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">aws_lambda_permission.api_gw_invoke will be created</span></span><br><span class="line">  + resource &quot;aws_lambda_permission&quot; &quot;api_gw_invoke&quot; &#123;</span><br><span class="line">      + action              = &quot;lambda:InvokeFunction&quot;</span><br><span class="line">      + function_name       = &quot;pile-test-function&quot;</span><br><span class="line">      + id                  = (known after apply)</span><br><span class="line">      + principal           = &quot;apigateway.amazonaws.com&quot;</span><br><span class="line">      + source_arn          = (known after apply)</span><br><span class="line">      + statement_id        = &quot;AllowAPIGatewayInvoke&quot;</span><br><span class="line">      + statement_id_prefix = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">Plan: 11 to add, 0 to change, 0 to destroy.</span><br><span class="line"></span><br><span class="line">Changes to Outputs:</span><br><span class="line">  + api_endpoint         = (known after apply)</span><br><span class="line">  + dynamodb_table_name  = &quot;pile-test-data-table&quot;</span><br><span class="line">  + lambda_function_name = &quot;pile-test-function&quot;</span><br><span class="line"></span><br><span class="line">（...中略...）</span><br><span class="line"></span><br><span class="line">Apply complete! Resources: 11 added, 0 changed, 0 destroyed.</span><br><span class="line"></span><br><span class="line">Outputs:</span><br><span class="line"></span><br><span class="line">api_endpoint = &quot;https://kiuv38oj5f.execute-api.ap-northeast-1.amazonaws.com/default&quot;</span><br><span class="line">dynamodb_table_name = &quot;pile-test-data-table&quot;</span><br><span class="line">lambda_function_name = &quot;pile-test-function&quot;</span><br></pre></td></tr></table></figure></div>

<p>まさかの一発成功でした🎉🎉🎉</p>
<h2 id="本当に最小権限になっているか？権限を外してみる">本当に最小権限になっているか？権限を外してみる</h2><p>作成するLambdaはVPC外で動作する定義になっています。そのため以下のEC2のネットワークインターフェースの権限は不要に見えましたので、外してみます。</p>
<p>以下をポリシーから削除。</p>
<ul>
<li><code>ec2:DescribeAccountAttributes</code></li>
<li><code>ec2:DescribeNetworkInterfaces</code></li>
</ul>
<p>一度、<code>terraform destory</code> してから再実行すると、やはり成功します。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ksso04-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ksso04-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform apply</span></span><br><span class="line">（...中略...）</span><br><span class="line"></span><br><span class="line">Apply complete! Resources: 11 added, 0 changed, 0 destroyed.</span><br><span class="line"></span><br><span class="line">Outputs:</span><br><span class="line"></span><br><span class="line">api_endpoint = &quot;https://j85kfvousg.execute-api.ap-northeast-1.amazonaws.com/default&quot;</span><br><span class="line">dynamodb_table_name = &quot;pile-test-data-table&quot;</span><br><span class="line">lambda_function_name = &quot;pile-test-function&quot;</span><br></pre></td></tr></table></figure></div>

<p>完全に厳格なツールという訳では無いようです。</p>
<h2 id="Pikeには絶対に無視されるであろうリソースを追加してみる">Pikeには絶対に無視されるであろうリソースを追加してみる</h2><p>以下のように、<code>local-exec</code> 経由でS3バケットを作成してみるコードを追加します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-ksso04-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ksso04-6" title="コードの折り返しを切り替える"></label><figcaption><span>main.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="comment"># ...中略 ...</span>
<span class="line"></span>
<span class="keyword">locals</span> {
  bucket_name = <span class="string">&quot;pike-<span class="variable">${random_string.bucket_suffix.result}</span>&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;terraform_data&quot;</span> <span class="string">&quot;s3_bucket_via_cli&quot;</span> {
  input = {
    bucket_name = local.bucket_name
  }
<span class="line"></span>
  provisioner <span class="string">&quot;local-exec&quot;</span> {
    command = &lt;&lt;EOT
      aws s3api create-bucket \
        --bucket ${self.input.bucket_name} \
        --create-bucket-configuration LocationConstraint=ap-northeast-<span class="number">1</span>
    EOT
    interpreter = [<span class="string">&quot;bash&quot;</span>, <span class="string">&quot;-c&quot;</span>]
    on_failure = fail
  }
<span class="line"></span>
  provisioner <span class="string">&quot;local-exec&quot;</span> {
    when    = destroy
    command = &lt;&lt;EOT
      aws s3api delete-bucket \
        --bucket ${self.input.bucket_name}
    EOT
    interpreter = [<span class="string">&quot;bash&quot;</span>, <span class="string">&quot;-c&quot;</span>]
  }
}
<span class="line"></span>
<span class="keyword">output</span> <span class="string">&quot;bucket_name_created_via_local_exec&quot;</span> {
  description = <span class="string">&quot;Name of the S3 bucket presumably created via local-exec&quot;</span>
  value       = local.bucket_name
}</pre></td></tr></tbody></table></figure></div>

<p>pikeコマンドを実行しますが、先ほどと差分は無しです（<code>local-exec</code> の実行コマンドをパースするわけがないので当たり前ですが）。</p>
<p>そのため、当たり前ですが <code>terraform apply</code> はS3実行権限が不足しているため失敗します。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ksso04-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ksso04-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform apply</span></span><br><span class="line">(...中略...)</span><br><span class="line"></span><br><span class="line">-/+ destroy and then create replacement</span><br><span class="line"></span><br><span class="line">Terraform will perform the following actions:</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">terraform_data.s3_bucket_via_cli is tainted, so must be replaced</span></span><br><span class="line">-/+ resource &quot;terraform_data&quot; &quot;s3_bucket_via_cli&quot; &#123;</span><br><span class="line">      ~ id     = &quot;210e249d-8405-3ed9-1506-1ff4fe12dc68&quot; -&gt; (known after apply)</span><br><span class="line">      ~ output = &#123;</span><br><span class="line">          - bucket_name = &quot;pike-p7niqibd&quot;</span><br><span class="line">        &#125; -&gt; (known after apply)</span><br><span class="line">        # (1 unchanged attribute hidden)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">Plan: 1 to add, 0 to change, 1 to destroy.</span><br><span class="line"></span><br><span class="line">(...中略...)</span><br><span class="line">terraform_data.s3_bucket_via_cli: Still creating... [10s elapsed]</span><br><span class="line">terraform_data.s3_bucket_via_cli: Still creating... [20s elapsed]</span><br><span class="line">terraform_data.s3_bucket_via_cli: Still creating... [30s elapsed]</span><br><span class="line"></span><br><span class="line">terraform_data.s3_bucket_via_cli (local-exec): An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::123456789012:user/xxx.xxxxx is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/my-pike-role</span><br><span class="line">╷</span><br><span class="line">│ Error: local-exec provisioner error</span><br><span class="line">│</span><br><span class="line">│   with terraform_data.s3_bucket_via_cli,</span><br><span class="line">│   on main.tf line 275, in resource &quot;terraform_data&quot; &quot;s3_bucket_via_cli&quot;:</span><br><span class="line">│  275:   provisioner &quot;local-exec&quot; &#123;</span><br><span class="line">│</span><br><span class="line">│ Error running command &#x27;      aws s3api create-bucket \</span><br><span class="line">│         --bucket pike-p7niqibd \</span><br><span class="line">│         --create-bucket-configuration LocationConstraint=ap-northeast-1 \</span><br><span class="line">│         --profile pike</span><br><span class="line">│ &#x27;: exit status 254. Output:</span><br><span class="line">│ An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::123456789012:user/xxx.xxxxx is not authorized to perform: sts:AssumeRole on resource:</span><br><span class="line">│ arn:aws:iam::123456789012:role/my-pike-role</span><br><span class="line">│</span><br><span class="line">╵</span><br></pre></td></tr></table></figure></div>

<h2 id="使ってみての所感">使ってみての所感</h2><p>簡易的な構成であれば、割と成功確立が高い可能性のかもしれない、と好意的に印象を持ちました。</p>
<p>しかし、Terraformの便利ツールを使ってみよう(第一回)〜Pike編〜 のような構成だと、何度か手動で権限を追加してやっと成功した、といった例もあるので、まずは自分たちの構成で素振りすると良いかもしれません（別のリージョンなどでapplyができるかといった具合でしょうか）。</p>
<p>まず、Pikeを使うようなモチベーションがあるという時点で、それなりの規模だと思うのでおそらく手動でメンテナンスが必要になってくるんだろうなという印象です。</p>
<p>他にもいくつか感想です。</p>
<ul>
<li>当たり前ですが、 local-exec などを経由して作成するリソースについては対応していません</li>
<li>今回は試していませんが、 Terraformモジュール 経由もちゃんとスキャンするようなログが出ていました</li>
<li>基本的にはリソースタイプに対するアクション（<code>ec2:RunInstances</code>など）に基づいていて、リソース名を制限したより細かい粒度では生成しないようです。これは予め理解して利用すると良いかなと思います（まぁ、これは許容しても良い気がします）</li>
<li>Terraform実行に必要な権限の一覧が出ると、どのようなサービスを利用しているかざっと分かるので、キャッチアップには良いかもなとは思いました（これは構成図をちゃんとメンテナンスした方が良いとは思いますが）</li>
</ul>
<h2 id="さいごに">さいごに</h2><p>Pikeを使ってみました。シンプルなTerraformコードでも微妙に不要な権限がついてたりしましたが、概ね想定通りの権限のみのポリシーを生成してくれました。</p>
<p>構成によっては、手動で追記しないと動かないというパターンもあるようなので、完全な自動化をめざすというよりは、手動で修正・レビューを含めたワークフローを作る必要があると思います。</p>
<p>実行速度はかなり高速で、インストールも簡単なので、まずはTerraformディレクトリで実行してみてお試ししやすいツールだと思いました。</p>
]]></content>
    <summary type="html">Pikeを触ってみた記事です。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="アクセス制御" scheme="https://future-architect.github.io/tags/%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E5%BE%A1/"/>
  </entry>
  <entry>
    <title>Terraform連載2025を開始します</title>
    <link href="https://future-architect.github.io/articles/20250331a/"/>
    <id>https://future-architect.github.io/articles/20250331a/</id>
    <published>2025-03-30T15:00:00.000Z</published>
    <updated>2025-03-30T15:00:00.000Z</updated>
    <author><name>admin</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250331a/terraform.png" alt="" width="800" height="418">

<p>うららかな春の陽気と共に、2025年度がスタートしました。</p>
<p>絶好のTerraform日和ということで、本日からTerraformをテーマにしたブログリレー「Terraform連載2025」を始めます。</p>
<h2 id="2024年の振り返り">2024年の振り返り</h2><p>2023年、2024年に続いて、3年連続開催となりめでたい限りです。ここ最近の（？）Terraformに関連したニュースといえば…</p>
<ul>
<li>2023年末の Terraformのフォークが「OpenTofu」としてLinux Foundation傘下で正式ローンチ。OpenTFから改名</li>
<li>2025年3月の IBM、HashiCorpの買収を完了したと発表</li>
</ul>
<p>…など、技術面以外の話題もいくつか上がっており、業界的な注目度の高さがうかがい知れます。</p>
<p>機能面に閉じると、以下のように前回連載以降に限っても活発にリリースされており、成熟度を上げつつも、ワクワクするようなアップデートも多いです。</p>
<ul>
<li>1.9リリース<ul>
<li>2024年6月26日 GA</li>
<li>変数のvalidationでオブジェクト参照が可能、templatestring関数の追加、moved blockで null_resource → terraform_dataリソースの移動ができるようになった、など</li>
</ul>
</li>
<li>1.10リリース<ul>
<li>2024年11月27日 GA</li>
<li>Ephemeral Valuesが利用可能になった!! S3 ネイティブ Stateロックが可能になった!! 性能向上、など</li>
</ul>
</li>
<li>1.11リリース<ul>
<li>2025年2月27日 GA</li>
<li>S3ネイティブStateロックがGAになり、DynamoDBのロックは非推奨になった、write-only attributes (書き込み専用属性) がリソースに追加された、<code>terraform test</code> で <code>-junit-xml</code> オプションがGAになり、JUnit XML 形式でのレポート出力が可能になった、など</li>
</ul>
</li>
</ul>
<p>1.12は2025年4月1日時点でバージョンがアルファですが、夏頃にはGAになるのではないでしょうか。</p>
<h2 id="連載スケジュール">連載スケジュール</h2><p>2025年は6名、全7回でお送りします。まだテーマ未定のところもありますが、公開時までのお楽しみということでしばらくお待ちください。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 日付 / 担当者 / 記事タイトル"><table>
<thead>
<tr>
<th>日付</th>
<th>担当者</th>
<th>記事タイトル</th>
</tr>
</thead>
<tbody><tr>
<td>3&#x2F;31（月）</td>
<td></td>
<td>インデックス記事（この記事です）</td>
</tr>
<tr>
<td>4&#x2F;1（火）</td>
<td>真野隼記さん</td>
<td>最小権限の原則の実現を支援するPike触ってみる</td>
</tr>
<tr>
<td>4&#x2F;3（木）</td>
<td>森友雅さん</td>
<td>【Terraform】プロビジョニングとはなんぞや</td>
</tr>
<tr>
<td>4&#x2F;4（金）</td>
<td>前原 応光さん</td>
<td>Terraform TIPS 集</td>
</tr>
<tr>
<td>4&#x2F;7（月）</td>
<td>大前七奈さん</td>
<td>Terraformでテーブル管理時のハマりどころ５選</td>
</tr>
<tr>
<td>4&#x2F;9（水）</td>
<td>伊藤太斉 さん</td>
<td>Terraform設計ガイドラインを公開しました</td>
</tr>
</tbody></table></div>
<p>チップス集やハマりどころ5選など実践的な部分から、セキュリティ戦略など理論的なところまで幅広いテーマを準備しました。</p>
<h2 id="さいごに">さいごに</h2><p>私の観測範囲では、Terraformはフューチャー社内で最も活用されているプロビジョニングツールだと思いますし、ナレッジを公開できる機会を設けられて嬉しいです。</p>
<p>引き続き、よろしくお願いします。</p>
]]></content>
    <summary type="html">うららかな春の陽気と共に、2025年度がスタートしました。絶好のTerraform日和ということで、本日からTerraformをテーマにしたブログリレー「Terraform連載2025」を始めます。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="インデックス" scheme="https://future-architect.github.io/tags/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9/"/>
  </entry>
  <entry>
    <title>Terraformでのループ処理と条件分岐</title>
    <link href="https://future-architect.github.io/articles/20240328b/"/>
    <id>https://future-architect.github.io/articles/20240328b/</id>
    <published>2024-03-27T15:00:01.000Z</published>
    <updated>2024-03-27T15:00:01.000Z</updated>
    <author><name>小林弘樹</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2024/20240328b/top.png" alt="" width="800" height="527">

<h2 id="はじめに">はじめに</h2><p>はじめまして！ TIG DXチームの小林と申します。</p>
<p>Terraformでは似たリソースを複数構築する際に、ループ処理や条件分岐を利用することで、コードの冗長化を防ぎ、可読性や保守性を上げることができます。</p>
<p>私自身まだTerraform歴半年ですが、初心者目線で「Terraformのコードをスマートに書きたい！」というモチベーションのもと本記事を書きました。</p>
<h2 id="サマリ">サマリ</h2><h3 id="ループ処理">ループ処理</h3><div class="scroll" tabindex="0" role="region" aria-label="表 方法 / 分類 / 主な用途（個人的なイメージ）"><table>
<thead>
<tr>
<th>方法</th>
<th>分類</th>
<th>主な用途（個人的なイメージ）</th>
</tr>
</thead>
<tbody><tr>
<td>count</td>
<td>メタ引数</td>
<td>・開発や検証用などで簡単なリソースを複数個作りたい場合 <br> ・将来的に数が増減しないようなリソースを作る場合</td>
</tr>
<tr>
<td>for_each</td>
<td>メタ引数</td>
<td>・ループ処理で複数リソースを作りたい場合は基本こちら</td>
</tr>
<tr>
<td>for</td>
<td>式</td>
<td>・フィルタリング機能を利用して条件によってリソース構築を制御したい場合 <br> ・既存の設定値や構築済リソースから任意のリストやマップを取得したい場合 <br> ・その他使ったら幸せになれる場合</td>
</tr>
<tr>
<td>dynamic block</td>
<td>式</td>
<td>・resource block内で同一のブロックを複数定義する場合 <br> ・可読性や保守性が落ちないことが明確な場合</td>
</tr>
</tbody></table></div>
<h3 id="条件分岐">条件分岐</h3><div class="scroll" tabindex="0" role="region" aria-label="表 方法 / 分類 / 主な用途（個人的なイメージ）"><table>
<thead>
<tr>
<th>方法</th>
<th>分類</th>
<th>主な用途（個人的なイメージ）</th>
</tr>
</thead>
<tbody><tr>
<td>三項演算子</td>
<td>式</td>
<td>・条件分岐を行いたい場合は基本こちら</td>
</tr>
<tr>
<td>for_each と for を併用</td>
<td>-</td>
<td>・forループ内の要素の特定条件でリソースを作り分ける場合</td>
</tr>
</tbody></table></div>
<h2 id="構築するリソース（ベース）">構築するリソース（ベース）</h2><p>本記事で構築するリソースはこちらです。</p>
<p>AWS上に10.10.0.0&#x2F;16 のVPC1つと、10.10.0.0&#x2F;24 ～ 10.10.3.0&#x2F;24 でサブネットを計4つ（public2つ、private2個想定）を各パターンで作成していきます。</p>
<p>まずはベースとして、シンプルにresource blockを羅列したものを記載しています。<br>（以降、サブネット部分の処理がメインのため、VPC部分の記述は省略します）。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-1" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_vpc&quot;</span> <span class="string">&quot;test-vpc&quot;</span> {
  cidr_block        = <span class="string">&quot;10.10.0.0/16&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public-1&quot;</span> {
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, <span class="number">0</span>)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public-2&quot;</span> {
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, <span class="number">1</span>)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;private-1&quot;</span> {
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, <span class="number">2</span>)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;private-2&quot;</span> {
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, <span class="number">3</span>)
}</pre></td></tr></tbody></table></figure></div>

<p>参考：cidrsubnet</p>
<h2 id="ループ処理-count">ループ処理(count)</h2><p><code>count</code>を利用すると、このように書くことができます。</p>
<p><code>count = x</code>とカウント回数を定義し、<code>count.index</code> で0からx回カウントアップする引数を指定できます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-2" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public&quot;</span> {
  count             = <span class="number">2</span>
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, count.index)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;private&quot;</span> {
  count             = <span class="number">2</span>
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, count.index + <span class="number">2</span>)
}</pre></td></tr></tbody></table></figure></div>

<p>publicとprivateの区別が無ければ、<code>count = 4</code>としてresource blockを1つで全サブネットを構築可能ですが、可読性や保守性が落ちるため分けています。</p>
<p>さて、ここでpublicのサブネットを1つ増やしたくなった場合はどうすれば良いでしょうか。</p>
<p>簡単な話ではありますが、以下のように<code>count</code>の値を3に修正することで、増やすことができます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-3" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public&quot;</span> {
  count             = <span class="number">3</span>
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, count.index)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;private&quot;</span> {
  count             = <span class="number">2</span>
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = <span class="string">&quot;ap-northeast-1a&quot;</span>
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, count.index + <span class="number">3</span>)
}</pre></td></tr></tbody></table></figure></div>

<p>しれっと修正しましたが、privateの方の <code>count.index + 2</code>も<code>count.index + 3</code>としています。</p>
<p>これを忘れると、以下のようなCIDRブロックのコンフリクトエラーが起きます。</p>
<blockquote>
<p>Error: creating EC2 Subnet: InvalidSubnet.Conflict: The CIDR ‘10.10.2.0&#x2F;24’ conflicts with another subnet</p>
</blockquote>
<p>countの使いづらいところは主にここだと思っています。数を増減させたいときに<code>count.index</code>の値の変動がどこまで影響するか、大規模や複雑なリソースでは把握が難しく、保守性が低下します。</p>
<p>また、<code>index</code>とあるように、<code>count</code>を利用して構築したリソースは配列として管理されます。</p>
<p>tfstateを覗いてみると、<code>index_key</code>というキーの値が0や1などの数値で存在します。</p>
<div class="code-block"><figure class="highlight json"><input type="checkbox" id="code-wrap-1i30o0z-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;managed&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;aws_subnet&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;public&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;provider&quot;</span><span class="punctuation">:</span> <span class="string">&quot;provider[\&quot;registry.terraform.io/hashicorp/aws\&quot;]&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;instances&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;index_key&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">（後略）</span><br></pre></td></tr></table></figure></div>

<p>これは、<code>index</code>の途中（↑のpublicサブネットで言うと<code>count.index</code>が1のサブネット）が削除された場合に、その後のリソースが全て作り直しになることを意味します。</p>
<p>ここも<code>count</code>の不便なところで、将来的に数が増減するようなリソースを構築する際は向いていません。</p>
<h2 id="ループ処理-for-each">ループ処理(for_each)</h2><p><code>count</code>の不便なところを解決したのが<code>for_each</code>だと思います。</p>
<p><code>for_each</code>を使うと以下のように書くことができます。</p>
<p>setやmapを定義して、その要素の数だけリソースを構築できます。</p>
<p>setやmapの値は<code>each.key</code>（setの値やmapのkey）や<code>each.value</code>（setの値やmapのvalue）を使って各変数に定義できます（＝setを使う場合は<code>each.key</code>と<code>each.value</code>は同じになります）。</p>
<p>mapが多重構造になっている場合は、以下のように<code>each.value.xxx</code>と書くことで変数に定義できます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-5" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;subnet&quot;</span> {
  for_each = tomap({
    public-<span class="number">1</span>a = {
      az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
      netnum = <span class="number">0</span>
    },
    public-<span class="number">1</span>c = {
      az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
      netnum = <span class="number">1</span>
    },
    private-<span class="number">1</span>a = {
      az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
      netnum = <span class="number">2</span>
    },
    private-<span class="number">1</span>c = {
      az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
      netnum = <span class="number">3</span>
    },
  })
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = each.value.az
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, each.value.netnum)
}</pre></td></tr></tbody></table></figure></div>

<p>可読性も比較的保たれたまま、resource blockを1つで書くことができました！</p>
<p>もちろん、publicとprivateでresource blockを分けても良いです。<code>count</code>と違い、リソースを増減させたい場合はmapに要素を追加するだけで良く、かつkeyで管理されているので既存のリソースに影響が及びません。</p>
<p>また<code>for_each</code>は複数の属性をループで回せて便利なので、<code>az</code>もループに含めてマルチAZ構成も実現しています。</p>
<h2 id="ループ処理-for">ループ処理(for)</h2><p>後述する<code>dynamic block</code>も同様ですが、<code>count</code>や<code>for_each</code>と違って<code>for</code>は「式」です。誤解を恐れず簡単に言うと、そもそもリソースを複数作るためのものではないということです。</p>
<p>具体的には、<code>for</code>はlist, set, tuple, map, objectを入力として、tupleもしくはobjectを出力するものです。そのため使い方は多様ですが、個人的に嬉しい使い方を2つ記載します。</p>
<h3 id="使い方（1）-特定条件でフィルタリングしてリソースを構築する">使い方（1） 特定条件でフィルタリングしてリソースを構築する</h3><p>構築するリソースが増えてくると、tfファイルの数やコードの行数が多くなって管理が大変でしょう。</p>
<p>そんな時は<code>local values</code>に各設定値を一元的に記載しておくと管理しやすくなるかもしれません。</p>
<p>以下の例は無理やり<code>for</code>を使いに行ってるので良い例ではありませんが、<code>local values</code>に条件となる値を設定しておき、resource blockではその条件によって構築や設定をするかを振り分ける、ということが可能です。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-6" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  subnet = {
    public-<span class="number">1</span>a = {
      public = true
      az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
      netnum = <span class="number">0</span>
    },
    public-<span class="number">1</span>c = {
      public = true
      az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
      netnum = <span class="number">1</span>
    },
    private-<span class="number">1</span>a = {
      public = false
      az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
      netnum = <span class="number">3</span>
    },
    private-<span class="number">1</span>c = {
      public = false
      az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
      netnum = <span class="number">4</span>
    },
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public&quot;</span> {
  for_each          = { for key, value in local.subnet : key =&gt; value if value.public == true }
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = each.value.az
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, each.value.netnum)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;private&quot;</span> {
  for_each          = { for key, value in local.subnet : key =&gt; value if value.public == false }
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = each.value.az
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, each.value.netnum)
}</pre></td></tr></tbody></table></figure></div>

<p>参考：Local Values</p>
<h3 id="使い方（2）-あるリソースの特定の設定値一覧を取得する">使い方（2） あるリソースの特定の設定値一覧を取得する</h3><p>例えばprivateサブネットからのみアクセス可能としたいリソース（EC2など）を構築し、そのセキュリティグループを構築するような場合を考えます。</p>
<p>サブネットは将来的に増減する可能性があり、それらのCIDRブロックを反映させて適切なインバウンドルールを設定する必要があります。</p>
<p>以下の<code>local.allow_cidr_block</code>のように記載することで、publicサブネットとprivateサブネットのCIDRブロック一覧が簡単に取得できます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-7" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  subnet = {
    public = {
      public-<span class="number">1</span>a = {
        az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
        netnum = <span class="number">0</span>
      },
      public-<span class="number">1</span>c = {
        az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
        netnum = <span class="number">1</span>
      },
    },
    private = {
      private-<span class="number">1</span>a = {
        az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
        netnum = <span class="number">2</span>
      },
      private-<span class="number">1</span>c = {
        az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
        netnum = <span class="number">3</span>
      },
    },
  }
  allow_cidr_block = {
    public = [
      for k, v in local.subnet.public :
      aws_subnet.public[k].cidr_block
    ]
    private = [
      for k, v in local.subnet.private :
      aws_subnet.private[k].cidr_block
    ]
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public&quot;</span> {
  for_each          = local.subnet.public
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = each.value.az
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, each.value.netnum)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;private&quot;</span> {
  for_each          = local.subnet.private
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = each.value.az
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, each.value.netnum)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_security_group&quot;</span> <span class="string">&quot;private_resource&quot;</span> {
  vpc_id = aws_vpc.test-vpc.id
  ingress {
    from_port   = <span class="number">443</span>
    to_port     = <span class="number">443</span>
    protocol    = <span class="string">&quot;tcp&quot;</span>
    cidr_blocks = local.allow_cidr_block.private
  }
}</pre></td></tr></tbody></table></figure></div>

<h2 id="ループ処理-dynamic-block">ループ処理(dynamic block)</h2><p><code>count</code>や<code>for_each</code>がresource blockを複数作成するときに利用したのに対し、<code>dynamic block</code>はresource block内のブロックを複製するときに利用できます。</p>
<p>例えば、<code>for</code>の使い方（2）で述べたようなセキュリティグループを構築する場合で、publicとprivate両方のサブネットからアクセス可能なセキュリティグループを作りたいとします。</p>
<p>この場合は、<code>ingress</code>のブロックを複製すると簡単に構築できるため、以下のように<code>dynamic block</code>が利用して書くことができます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-8" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  subnet = {
    public = {
      public-<span class="number">1</span>a = {
        az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
        netnum = <span class="number">0</span>
      },
      public-<span class="number">1</span>c = {
        az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
        netnum = <span class="number">1</span>
      },
    },
    private = {
      private-<span class="number">1</span>a = {
        az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
        netnum = <span class="number">2</span>
      },
      private-<span class="number">1</span>c = {
        az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
        netnum = <span class="number">3</span>
      },
    },
  }
  allow_cidr_block = {
    public = [
      for k, v in local.subnet.public :
      aws_subnet.public[k].cidr_block
    ]
    private = [
      for k, v in local.subnet.private :
      aws_subnet.private[k].cidr_block
    ]
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public&quot;</span> {
  for_each          = local.subnet.public
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = each.value.az
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, each.value.netnum)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;private&quot;</span> {
  for_each          = local.subnet.private
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = each.value.az
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, each.value.netnum)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_security_group&quot;</span> <span class="string">&quot;public_resource&quot;</span> {
  vpc_id = aws_vpc.test-vpc.id
  dynamic <span class="string">&quot;ingress&quot;</span> {
    for_each = local.allow_cidr_block
    content {
      from_port   = <span class="number">443</span>
      to_port     = <span class="number">443</span>
      protocol    = <span class="string">&quot;tcp&quot;</span>
      cidr_blocks = ingress.value
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<p>ただし、<code>dynamic block</code>は冒頭の説明でも述べた通り「resource block内のブロック」を複製するもので、単純なkey : value の形で定義する変数では利用できなかったりと、使い方が限定的です（本記事では主にサブネットを複製してきましたが、サブネットの複製にdynamic blockは使えません）。</p>
<p>もう少しだけ<code>dynamic block</code>の使い道を考えます。</p>
<p>実践ではセキュリティグループは1個ということは基本ありえず、様々なリソース用に色々なセキュリティグループを構築することになるでしょう。</p>
<p>また、それぞれのセキュリティグループにはルールはいくつか存在し、CIDRブロックでなくセキュリティグループがソースになったり、ポートやプロトコルが異なっていたりもするでしょう。そうなると、以下のように全ての設定値を<code>local values</code>にmapとしてまとめておくのが良いでしょう（長くなるのでサブネット部分の記述も省略しました）。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-9" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-9" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  subnet = {
    public = {
      public-<span class="number">1</span>a = {
        az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
        netnum = <span class="number">0</span>
      },
      public-<span class="number">1</span>c = {
        az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
        netnum = <span class="number">1</span>
      },
    },
    private = {
      private-<span class="number">1</span>a = {
        az     = <span class="string">&quot;ap-northeast-1a&quot;</span>
        netnum = <span class="number">2</span>
      },
      private-<span class="number">1</span>c = {
        az     = <span class="string">&quot;ap-northeast-1c&quot;</span>
        netnum = <span class="number">3</span>
      },
    },
  }
  allow_cidr_block = {
    public = [
      for k, v in local.subnet.public :
      aws_subnet.public[k].cidr_block
    ]
    private = [
      for k, v in local.subnet.private :
      aws_subnet.private[k].cidr_block
    ]
  }
  security_group = {
    ec2_a = {
      ingress_1 = {
        from_port       = <span class="number">22</span>
        to_port         = <span class="number">22</span>
        protocol        = <span class="string">&quot;tcp&quot;</span>
        cidr_blocks     = local.allow_cidr_block.private
        security_groups = null
      },
      ingress_2 = {
        from_port       = <span class="number">80</span>
        to_port         = <span class="number">80</span>
        protocol        = <span class="string">&quot;tcp&quot;</span>
        cidr_blocks     = null
        security_groups = [aws_security_group.alb.id]
      }
    }
    ec2_b = {
      ingress_1 = {
        from_port       = <span class="number">22</span>
        to_port         = <span class="number">22</span>
        protocol        = <span class="string">&quot;tcp&quot;</span>
        cidr_blocks     = local.allow_cidr_block.private
        security_groups = null
      }
    }
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_security_group&quot;</span> <span class="string">&quot;ec2_a&quot;</span> {
  vpc_id = aws_vpc.test-vpc.id
  dynamic <span class="string">&quot;ingress&quot;</span> {
    for_each = local.security_group.ec2_a
    content {
      from_port       = ingress.value.from_port
      to_port         = ingress.value.to_port
      protocol        = ingress.value.protocol
      cidr_blocks     = ingress.value.cidr_blocks != null ? ingress.value.cidr_blocks : null
      security_groups = ingress.value.security_groups != null ? ingress.value.security_groups : null
    }
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_security_group&quot;</span> <span class="string">&quot;ec2_b&quot;</span> {
  vpc_id = aws_vpc.test-vpc.id
  dynamic <span class="string">&quot;ingress&quot;</span> {
    for_each = local.security_group.ec2_b
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks     = ingress.value.cidr_blocks != null ? ingress.value.cidr_blocks : null
      security_groups = ingress.value.security_groups != null ? ingress.value.security_groups : null
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<p>…なんとか書けました。</p>
<p>ご覧の通り<code>cidr_blocks</code>と<code>security_groups</code>はどちらかのみ設定するため、それを実現させるために後述する<code>三項演算子</code>を用いたり、<code>local values</code>にもわざわざnullとして定義しています。</p>
<p>さて、ループ処理の目的である<code>コードの冗長化を防ぎ、可読性や保守性を上げる</code>ことはできたでしょうか。<code>dynamic block</code>を使わずにシンプルに<code>ingress</code>のブロックを羅列しても行数はむしろ減りますし、ループや条件分岐がなくなる分、可読性や保守性も上がりそうです。</p>
<p>ちなみに公式のベストプラクティスでも<code>dynamic block</code>の使い過ぎは推奨されておらず、モジュールの再利用を目的としてシンプルな構成にしたいような場合に、利用することを推奨しています。</p>
<p>このため、<code>dynamic block</code>は可読性や保守性を考えて慎重に利用するのが良いと思われます。</p>
<h2 id="条件分岐-三項演算子">条件分岐(三項演算子)</h2><p>Terraformでは条件分岐したい場合は基本1通りで、この三項演算子を利用します。</p>
<p>構文は以下の通りで、<code>condition</code>に記載した条件がtrueなら<code>true_val</code>が、falseなら<code>false_val</code>が採用されます。</p>
<blockquote>
<p>condition ? true_val : false_val</p>
</blockquote>
<p>参考：Conditional Expressions</p>
<p>簡単な例では、環境ごとにリソースの数を変えるような場合があります。</p>
<p>例えば本番環境は冗長化したいのでマルチAZで構築するが、開発&#x2F;検証環境はシングルAZで良い場合などに、環境名ごとにcountの値を変えるような操作が可能です。<br>以下のように書くことで、環境名（<code>local.env</code>）の値を変えるだけで本番環境と開発&#x2F;検証環境で、リソース数の切り替えができます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1i30o0z-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1i30o0z-10" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  env = <span class="string">&quot;prod&quot;</span>
  az = [
    <span class="string">&quot;ap-northeast-1a&quot;</span>,
    <span class="string">&quot;ap-northeast-1c&quot;</span>,
    <span class="string">&quot;ap-northeast-1d&quot;</span>
  ]
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;public&quot;</span> {
  count             = local.env == <span class="string">&quot;prod&quot;</span> ? <span class="number">2</span> : <span class="number">1</span>
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = local.az[count.index]
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, count.index)
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_subnet&quot;</span> <span class="string">&quot;private&quot;</span> {
  count             = local.env == <span class="string">&quot;prod&quot;</span> ? <span class="number">2</span> : <span class="number">1</span>
  vpc_id            = aws_vpc.test-vpc.id
  availability_zone = local.az[count.index]
  cidr_block        = cidrsubnet(aws_vpc.test-vpc.cidr_block, <span class="number">8</span>, count.index + <span class="number">2</span>)
}</pre></td></tr></tbody></table></figure></div>

<h2 id="条件分岐-for-each-と-for-を併用">条件分岐(for_each と for を併用)</h2><p>こちらは、<code>for</code>の部分で記載したものの再掲となります。</p>
<p>Terraformに一般的なプログラミング言語でいうif文はありませんが、<code>for</code>文の中のifによってループ処理の中で条件分岐できます。</p>
<p>使い方はfor（使い方① 特定条件でフィルタリングしてリソースを構築する）をご参照ください。</p>
<h2 id="さいごに">さいごに</h2><p>Terraformにおけるループ処理と条件分岐をまとめました。</p>
<p>自分も例外ではなく、初心者はまずTerraformの構文に慣れるところが難しいかと思います。</p>
<p>本記事が同じようなTerraform初心者の一助となれば幸いです。</p>
]]></content>
    <summary type="html">Terraformでは似たリソースを複数構築する際に、ループ処理や条件分岐を利用することで、コードの冗長化を防ぎ、可読性や保守性を上げることができます。初心者目線で「Terraformのコードをスマートに書きたい！」というモチベーションのもと本記事を書いてみました。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="初心者向け" scheme="https://future-architect.github.io/tags/%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91/"/>
  </entry>
  <entry>
    <title>手動運用しているCloudflareをTerraformでInfrastructure as Codeする</title>
    <link href="https://future-architect.github.io/articles/20240327a/"/>
    <id>https://future-architect.github.io/articles/20240327a/</id>
    <published>2024-03-26T15:00:00.000Z</published>
    <updated>2024-03-26T15:00:00.000Z</updated>
    <author><name>大岩潤矢</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>この記事は、Terraform連載2024の10記事目です。</p>
<p>みなさんこんにちは。TIG所属の大岩潤矢( @920OJ ) です。</p>
<p>本記事ではCloudflareで管理しているドメインのDNS設定やCloudflare Pages等のサービスの設定を、Terraform管理に移行した際の手順などを記載します。</p>
<h2 id="背景">背景</h2><p>私は個人開発や自身のポートフォリオWebサイトの公開のために、 <code>920oj.net</code> というドメインを所持しています。このドメインはGoogle Domainsで取得し、ネームサーバを変更してCloudflare上でDNSレコードを管理しています。</p>
<p>また、自身の ポートフォリオサイトWebサイト はCloudflare Pagesでホスティングしています。転送量無制限で月500ビルドまで無料というのはお財布に優しくありがたいです。</p>
<p>一方で、Webの管理画面からCloudflareでのドメイン管理や各種設定を変更する際、バージョン履歴を表示する機能がないため、一度変更してしまったものは元に戻すことができません。例えばDNS設定を更新する際、間違えた値に更新してしまった場合、元の値を記憶していない限りは戻せなくなってしまいます。バージョニング機能はあるものの、Enterpriseプランのみでしか使えないようです。</p>
<p>そこで利用したいと思い立ったのがTerraformです。CloudflareをTerrafromで構築し、そのコードをGit等で管理することにより、変更をバージョン管理できます。記事タイトルにある通り、「Infrastructure as Codeする」ということです。</p>
<p>一方でCloudflareはすでに利用中であるため、これまで手作業で実施してきた設定をTerraformへimportして管理することになります。</p>
<p>今回移行するものは、各ドメインのDNSレコードと、Cloudflare Pagesでホスティングしているプロジェクトの2つとします。</p>
<h2 id="CloudflareをTerraformで管理するための前準備">CloudflareをTerraformで管理するための前準備</h2><p>早速、既存リソースをTerraformで管理するための手順を紹介します。まずは下準備として、以下を実施します。</p>
<ul>
<li>tfstate管理用のR2バケットを作成</li>
<li>APIトークンの発行</li>
<li>環境変数の設定</li>
</ul>
<h3 id="Cloudflare-R2へtfstate管理用のバケットを作成する">Cloudflare R2へtfstate管理用のバケットを作成する</h3><p>CloudflareにはR2というS3互換のストレージサービスがあり、tfstateはこのR2の中で管理する方針とします。R2でtfstateを管理する方法については、すでにこのテックブログで記事があるので、これを参考にします。</p>
<p>https://future-architect.github.io/articles/20231016a/</p>
<p>まずはCloudflareの管理画面にログインし、R2を選択→「Add R2 subscription to my account」を押下します。</p>
<img fetchpriority="high" src="/images/2024/20240327a/image.png" alt="Add R2 subscription to my accountをクリック" width="1200" height="615">

<p>「Create bucket」 を押下します。</p>
<img src="/images/2024/20240327a/image_2.png" alt="Create bucketをクリック" width="965" height="405" loading="lazy">

<p>バケット名を入力し、Locationは「Automatic」を選択します。最後に「Create bucket」を押下すれば、バケットが出来上がります。</p>
<img src="/images/2024/20240327a/image_3.png" alt="oj-cf-tfstateというバケット名を入力" width="952" height="832" loading="lazy">

<img src="/images/2024/20240327a/image_4.png" alt="Automaticのチェックボックスを選択" width="1200" height="612" loading="lazy">

<h3 id="APIトークンを発行する">APIトークンを発行する</h3><p>CloudflareをTerraform管理、すなわちAPIで操作する場合、APIトークンの発行が必須です。</p>
<p>右上ユーザアイコンより「My Profile」を押下→左メニューからAPI Tokensを選び、「Create Token」を押下します。</p>
<img src="/images/2024/20240327a/image_5.png" alt="Cretate Tokenをクリック" width="1200" height="615" loading="lazy">

<p>Create Custom Tokenの「Get started」を押下します。</p>
<img src="/images/2024/20240327a/image_6.png" alt="Get startedボタンをクリック" width="866" height="217" loading="lazy">

<p>各種設定値を入力します。</p>
<ul>
<li>Token name: 任意の名前を入力</li>
<li>Permissions: Terraform経由で操作するサービスを選び、それぞれEditの権限を指定する<ul>
<li>どのサービスで何の権限が必要かはドキュメントにまとまっているので参照のこと</li>
</ul>
</li>
<li>Account Resources: 自分が権限を持っているアカウント（メールアドレス）を選択可能。ここではAll accountsとしたが、複数のアカウントがある場合はここで絞っておくことが好ましい</li>
<li>Zone Resources: アカウントの中のドメインを選択できる。ここではAll zonesとしたが、操作できるドメインを絞りたいときはここで指定する</li>
<li>Client IP Address Filtering: 仮に操作されるIPアドレスが決まっている場合はここで指定する。何も入力しなければ、すべてのIPアドレスからアクセスを許容する</li>
<li>すべて入力できたら「Continue to summary」を押下</li>
</ul>
<img src="/images/2024/20240327a/image_7.png" alt="" width="1200" height="764" loading="lazy">

<img src="/images/2024/20240327a/image_8.png" alt="" width="1136" height="784" loading="lazy">

<p>設定内容が表示されるので、問題なければ「Create Token」を押下します。</p>
<img src="/images/2024/20240327a/image_9.png" alt="Create Tokneをクリック" width="1155" height="561" loading="lazy">

<p>APIトークンが表示されますが、このままではR2のAccess KeyおよびSecretが表示されないため、再度作り直します。このページでのコピーは不要です。</p>
<img src="/images/2024/20240327a/image_10.png" alt="ima" width="1200" height="550" loading="lazy">

<p>R2の管理ページを開き、右側メニューより「Manage R2 API Tokens」を選びます。</p>
<img src="/images/2024/20240327a/image_11.png" alt="" width="1200" height="615" loading="lazy">

<p>先ほど作成したトークンの「・・・」を押下し、「Roll」を選択。注意書きを読み、「Roll」を押下します。</p>
<img src="/images/2024/20240327a/image_12.png" alt="" width="1200" height="404" loading="lazy">

<img src="/images/2024/20240327a/image_13.png" alt="" width="775" height="540" loading="lazy">

<p>「API Token」「Access Key ID」「Secret Access Key」「R2のエンドポイント」が表示されるので、これらをすべてコピーしておきましょう。</p>
<img src="/images/2024/20240327a/image_14.png" alt="" width="1200" height="680" loading="lazy">

<h3 id="環境変数の設定">環境変数の設定</h3><p>ここからは操作するPCでの作業となります。まずはターミナルを開き、環境変数をセットします。</p>
<p>先ほどコピーしたAPIトークン等認証情報を、環境変数としてセットします。セットするキーと値は以下のとおりです。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 No. / 環境変数名 / 値"><table>
<thead>
<tr>
<th>No.</th>
<th>環境変数名</th>
<th>値</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>AWS_ACCOUNT_ID</td>
<td>CloudflareのアカウントID</td>
</tr>
<tr>
<td>2</td>
<td>AWS_ACCESS_KEY_ID</td>
<td>APIトークンで払い出したアクセスキーID</td>
</tr>
<tr>
<td>3</td>
<td>AWS_SECRET_ACCESS_KEY</td>
<td>APIトークンで払い出したシークレットアクセスキー</td>
</tr>
<tr>
<td>4</td>
<td>CLOUDFLARE_ACCOUNT_ID</td>
<td>CloudflareのアカウントID</td>
</tr>
<tr>
<td>5</td>
<td>CLOUDFLARE_API_TOKEN</td>
<td>アクセスキー、シークレットアクセスキーと共に払い出したトークン</td>
</tr>
</tbody></table></div>
<p>1、4については管理画面より確認できます。2、3、5については前項でコピーしたものをセットしましょう。</p>
<p>以下のようにシェルスクリプトにまとめて、 <code>source set-env.sh</code> のコマンドで設定できるようにすると楽です。</p>
<figure class="highlight bash"><figcaption><span>set-env.sh</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> AWS_ACCOUNT_ID=xxxxxxxxxxxxxxx</span><br><span class="line"><span class="built_in">export</span> AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxx</span><br><span class="line"><span class="built_in">export</span> AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxx</span><br><span class="line"><span class="built_in">export</span> CLOUDFLARE_ACCOUNT_ID=xxxxxxxxxxxxxxx</span><br><span class="line"><span class="built_in">export</span> CLOUDFLARE_API_TOKEN=xxxxxxxxxxxxxxx</span><br></pre></td></tr></table></figure>

<h2 id="CloudflareをTerraform管理する">CloudflareをTerraform管理する</h2><p>ここからが本題で、いよいよCloudflare上にあるリソースをTerraform管理にするため、インポート等の作業を実施していきます。</p>
<h3 id="ディレクトリ構成">ディレクトリ構成</h3><p>任意の場所にCloudflareのTerraform管理用のディレクトリを作成します。これをgit管理とし、その配下のディレクトリ構造・ファイル構成は以下の形とします。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">.</span><br><span class="line">├── domains</span><br><span class="line">│   └── 920oj-net</span><br><span class="line">│       ├── local.tf</span><br><span class="line">│       ├── record.tf</span><br><span class="line">│       └── setup.tf</span><br><span class="line">└── global</span><br><span class="line">    └── pages</span><br><span class="line">        └── 920oj-net</span><br><span class="line">            ├── local.tf</span><br><span class="line">            ├── pages_domain.tf</span><br><span class="line">            ├── pages_project.tf</span><br><span class="line">            └── setup.tf</span><br><span class="line"></span><br><span class="line">5 directories, 7 files</span><br></pre></td></tr></table></figure>

<p>ドメイン（ゾーン）管理は <code>domains/</code> 配下で実施し、利用するドメインごとにフォルダを切り、それぞれでtfstateを分ける形とします。</p>
<p>Cloudflare Pagesはアカウントでグローバルに管理するため、 <code>global/</code> 配下で管理し、 <code>pages</code> ディレクトリを切り、さらにプロジェクトごとにディレクトリを分ける形式とします。</p>
<h3 id="各ディレクトリのセットアップ">各ディレクトリのセットアップ</h3><p>Terraformのバージョン情報やプロパイダの設定、tfstateの配置場所等の初期設定に必要なファイルは、 <code>setup.tf</code> にまとめて、各ディレクトリに配置します。</p>
<p><code>key</code> はディレクトリごとに分けておき、tfstateが分けられるようにします。自分は以下のようなルールで運用しています。</p>
<ul>
<li>ドメイン: <code>domains/ドメイン名.tfstate</code></li>
<li>Pages: <code>global/pages/プロジェクト名.tfstate</code></li>
<li>Workers: <code>global/workers/プロジェクト名.tfstate</code></li>
</ul>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-7dqq3k-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-1" title="コードの折り返しを切り替える"></label><figcaption><span>setup.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">terraform</span> {
  <span class="comment"># terraformのバージョン設定</span>
  required_version = <span class="string">&quot;~&gt; 1.7.5&quot;</span>
<span class="line"></span>
  // cloudflareプロバイダを利用
  required_providers {
    cloudflare = {
      source  = <span class="string">&quot;cloudflare/cloudflare&quot;</span>
      version = <span class="string">&quot;~&gt; 4.26.0&quot;</span>
    }
  }
<span class="line"></span>
  <span class="comment"># tfstateの保存先の設定。R2 Storageを使用する</span>
  backend <span class="string">&quot;s3&quot;</span> {
    endpoints = {
      s3 = <span class="string">&quot;https://&lt;アカウントID&gt;.r2.cloudflarestorage.com&quot;</span>
    }
    bucket                      = <span class="string">&quot;oj-cf-tfstate&quot;</span> <span class="comment"># ここでバケット名を指定</span>
    key                         = <span class="string">&quot;domains/920oj-net.tfstate&quot;</span> <span class="comment"># ディレクトリごとにキーを変更</span>
    region                      = <span class="string">&quot;us-east-1&quot;</span> <span class="comment"># 任意の値でOK</span>
    skip_credentials_validation = true
    skip_requesting_account_id  = true
    skip_s3_checksum            = true
  }
}
<span class="line"></span>
<span class="keyword">provider</span> <span class="string">&quot;cloudflare&quot;</span> {}</pre></td></tr></tbody></table></figure></div>

<p>backend の設定で、 <code>skip_credentials_validation</code> と <code>skip_requesting_account_id</code> 、 <code>skip_s3_checksum</code> の3つを <code>true</code> にする必要があります。</p>
<h3 id="ドメインのDNSレコードをimportする">ドメインのDNSレコードをimportする</h3><p>Cloudflareのimportには、Terraform公式で用意されているimportコマンドを利用するほか、Cloudflareが独自に提供している cf-terraforming というツールを利用できます。</p>
<p>cf-terraformingについては、これまた伊藤さんが書かれている記事があるので、こちらも読んでみてください。</p>
<p>https://future-architect.github.io/articles/20230502a/</p>
<p>実際に現在の設定をimportしてみましょう。</p>
<h4 id="generate">generate</h4><p>まずは現在の設定をTerraformの記述に落とし込んでくれる <code>generate</code> コマンドを試します。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-7dqq3k-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">cf-terraforming generate --resource-type <span class="string">&quot;cloudflare_record&quot;</span> --zone <span class="string">&quot;ゾーンID&quot;</span></span><br></pre></td></tr></table></figure></div>

<ul>
<li><code>--resource-type</code> オプションで取得したいリソースを指定します。今回はDNS設定を取得してみるので、 <code>cloudflare_record</code> を指定します。<ul>
<li>取得できるリソース一覧はドキュメントに無かったので、ソースコードを参照します。</li>
</ul>
</li>
<li><code>--zone</code> オプションで取得したいzoneのIDを指定します。</li>
</ul>
<p>実行してみたところ、以下のエラーが出ました。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-7dqq3k-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">FATA[0000] --account and --zone are mutually exclusive, support <span class="keyword">for</span> both is deprecated</span><br></pre></td></tr></table></figure></div>

<p>どうやら先程セットした環境変数 <code>CLOUDFLARE_ACCOUNT_ID</code> がセットされていると正常に動いてくれなさそうなので、一旦 <code>unset CLOUDFLARE_ACCOUNT_ID</code> コマンドで環境変数を外しておきます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-7dqq3k-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash">cf-terraforming generate --resource-type <span class="string">&quot;cloudflare_record&quot;</span> --zone <span class="string">&quot;ゾーンID&quot;</span></span></span><br><span class="line">resource &quot;cloudflare_record&quot; &quot;terraform_managed_resource_xxxxxxxxxxx&quot; &#123;</span><br><span class="line">  name    = &quot;920oj.net&quot;</span><br><span class="line">  proxied = true</span><br><span class="line">  ttl     = 1</span><br><span class="line">  type    = &quot;CNAME&quot;</span><br><span class="line">  value   = &quot;920oj-net.pages.dev&quot;</span><br><span class="line">  zone_id = &quot;ゾーンID&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;cloudflare_record&quot; &quot;terraform_managed_resource_yyyyyyyyyyy&quot; &#123;</span><br><span class="line">  name     = &quot;920oj.net&quot;</span><br><span class="line">  priority = 10</span><br><span class="line">  proxied  = false</span><br><span class="line">  ttl      = 1</span><br><span class="line">  type     = &quot;MX&quot;</span><br><span class="line">  value    = &quot;example.com&quot;</span><br><span class="line">  zone_id  = &quot;ゾーンID&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>問題なければ、先程のコマンドの末尾に <code>&gt;&gt; record.tf</code> をつけてファイルに書き出しましょう。</p>
<p>このままだとリソース名がランダムなものになっているので、わかりやすいように名前を変えると管理しやすいです。</p>
<ul>
<li>例: ルートドメインのCNAMEレコード: <code>cname_root</code></li>
<li>例: <code>hoge</code> という名前のAレコード: <code>a_hoge</code></li>
</ul>
<p>また、zone_idやルートドメイン名は何度か記述することになるので、local変数に定義しておくとミスが減ります。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-7dqq3k-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-5" title="コードの折り返しを切り替える"></label><figcaption><span>local.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  zone_id = <span class="string">&quot;ゾーンID&quot;</span>
  root_domain = <span class="string">&quot;920oj.net&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-7dqq3k-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-6" title="コードの折り返しを切り替える"></label><figcaption><span>record.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;cloudflare_record&quot;</span> <span class="string">&quot;cname_root&quot;</span> {
  name    = local.root_domain
  proxied = true
  ttl     = <span class="number">1</span>
  type    = <span class="string">&quot;CNAME&quot;</span>
  value   = <span class="string">&quot;920oj-net.pages.dev&quot;</span>
  zone_id = local.zone_id
}
<span class="line"></span>
...以下略</pre></td></tr></tbody></table></figure></div>

<h4 id="import">import</h4><p>このままでは新規追加した分がそのまま新規として認識されてしまうので、すでに作成されているリソースについてはimportしてtfstateへ反映させる必要があります。</p>
<p>importするためのコマンドはcf-terraformingを利用して出力できます。ただし今回はリソース名をわかりやすく変更したため、コマンドを修正します。</p>
<p>まずはcf-terraformingを利用してコマンドを出力してみましょう。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-7dqq3k-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"> % </span><span class="language-bash">cf-terraforming import --resource-type <span class="string">&quot;cloudflare_record&quot;</span> --zone <span class="string">&quot;ゾーンID&quot;</span></span></span><br><span class="line">terraform import cloudflare_record.terraform_managed_resource_xxxxxxxxxx ゾーンID/xxxxxxxxxx</span><br><span class="line">terraform import cloudflare_record.terraform_managed_resource_yyyyyyyyyy ゾーンID/yyyyyyyyyy</span><br></pre></td></tr></table></figure></div>

<p>出力されたコマンドをもとに、リソース名を変更した上で、シェルスクリプトファイルとして保存します。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-7dqq3k-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-8" title="コードの折り返しを切り替える"></label><figcaption><span>import.sh</span></figcaption><table><tr><td class="code"><pre><span class="line">terraform import cloudflare_record.terraform_managed_resource_cname_root ゾーンID/xxxxxxxxxx</span><br><span class="line">terraform import cloudflare_record.terraform_managed_resource_mx_root ゾーンID/yyyyyyyyyy</span><br></pre></td></tr></table></figure></div>

<p>これを実行してみましょう。 Import successful! と表示されれば、インポート完了です。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-7dqq3k-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"> % </span><span class="language-bash">./import.sh</span></span><br><span class="line">cloudflare_record.cname_root: Importing from ID &quot;ゾーンID/xxxxxxxxxx&quot;...</span><br><span class="line">cloudflare_record.cname_root: Import prepared!</span><br><span class="line">  Prepared cloudflare_record for import</span><br><span class="line">cloudflare_record.cname_root: Refreshing state... [id=xxxxxxxxxxxxxxxxxxxxxx]</span><br><span class="line"></span><br><span class="line">・・・中略・・・</span><br><span class="line"></span><br><span class="line">Import successful!</span><br><span class="line"></span><br><span class="line">The resources that were imported are shown above. These resources are now in</span><br><span class="line">your Terraform state and will henceforth be managed by Terraform.</span><br></pre></td></tr></table></figure></div>

<p>ここで <code>terraform plan</code> を実行してみましょう。先ほどimportしたものが表示され、最後にNo chanegsと表示されれば、無事反映に成功しています。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-7dqq3k-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"> % </span><span class="language-bash">terraform plan</span></span><br><span class="line">cloudflare_record.cname_root: Refreshing state... [id=xxxxxxxxxxxxxxxxxxxxxx]</span><br><span class="line">cloudflare_record.mx_root: Refreshing state... [id=xxxxxxxxxxxxxxxxxxxxxx]</span><br><span class="line">・・・中略・・・</span><br><span class="line"></span><br><span class="line">No changes. Your infrastructure matches the configuration.</span><br><span class="line"></span><br><span class="line">Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.</span><br></pre></td></tr></table></figure></div>

<h3 id="Cloudflare-Pagesのimport">Cloudflare Pagesのimport</h3><p>続いてはCloudflare PagesをTerraform管理下となるよう設定します。</p>
<p>Pagesはドメイン（ゾーン）単位でなくアカウント単位での管理となるため、先ほどunsetした <code>CLOUDFLARE_ACCOUNT_ID</code> を再セットします。</p>
<p>また、Cloudflare Pagesはcf-terraformingが対応していないため、Terraform v1.5から追加されたImportブロックを利用してインポートします。</p>
<h4 id="importブロックの作成">importブロックの作成</h4><p>自分の場合、 <code>920oj.net</code> のドメインで、プロジェクト <code>920oj-net</code> を設定しています。これをインポートしてみましょう。</p>
<img src="/images/2024/20240327a/image_15.png" alt="image.png" width="873" height="200" loading="lazy">

<p>Cloudflare Pagesは、 <code>cloudflare_pages_domain</code> リソースと <code>cloudflarepages_project</code> リソースから構築されます。</p>
<p>まずは <code>import.tf</code> を作成し、importブロックを記載します。 <code>local.account_id</code> でアカウントIDが呼び出せるようにしています。</p>
<p><code>cloudflare_pages_domain</code> のインポートでは、<code>to</code> にはimportする対象のリソース名を、 <code>id</code> には <code>&lt;アカウントID&gt;/&lt;プロジェクト名&gt;/&lt;設定しているドメイン名&gt;</code> を記載します。</p>
<p><code>cloudflare_pages_project</code> のインポートでは、 <code>to</code> にはimportする対象のリソース名を、 <code>id</code> には <code>&lt;アカウントID&gt;/&lt;プロジェクト名&gt;</code> を記載します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-7dqq3k-11" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-11" title="コードの折り返しを切り替える"></label><figcaption><span>import.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="comment"># cloudflare_pages_domain のインポート</span>
import {
  to = cloudflare_pages_domain.domain-<span class="number">920</span>oj-net  <span class="comment"># 対象のリソース名</span>
  id = <span class="string">&quot;<span class="variable">${local.account_id}</span>/920oj-net/920oj.net&quot;</span> <span class="comment"># ドメイン名</span>
}
<span class="line"></span>
<span class="comment"># cloudflare_pages_project のインポート</span>
import {
  to = cloudflare_pages_project.project-<span class="number">920</span>oj-net <span class="comment"># 対象のリソース名</span>
  id = <span class="string">&quot;<span class="variable">${local.account_id}</span>/920oj-net&quot;</span>            <span class="comment"># プロジェクト名</span>
}</pre></td></tr></tbody></table></figure></div>

<h4 id="HCLコードの自動生成">HCLコードの自動生成</h4><p>この状態で <code>terraform plan -generate-config-out=generate.tf</code> コマンドを実行します。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-7dqq3k-12" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-12" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash">terraform plan -generate-config-out=generate.tf</span></span><br><span class="line">cloudflare_pages_domain.domain-920oj-net: Preparing import... [id=xxxxxxxxxxxxxxx/920oj-net/920oj.net]</span><br><span class="line">cloudflare_pages_project.project-920oj-net: Preparing import... [id=xxxxxxxxxxxxxxx/920oj-net]</span><br><span class="line">cloudflare_pages_domain.domain-920oj-net: Refreshing state... [id=xxxxxxxxxxxxxxxxxxxxx]</span><br><span class="line">cloudflare_pages_project.project-920oj-net: Refreshing state... [id=920oj-net]</span><br><span class="line"></span><br><span class="line">Terraform will perform the following actions:</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">cloudflare_pages_domain.domain-920oj-net will be imported</span></span><br><span class="line">    resource &quot;cloudflare_pages_domain&quot; &quot;domain-920oj-net&quot; &#123;</span><br><span class="line">        account_id   = &quot;xxxxxxxxxxxxxxxxxxxxxxx&quot;</span><br><span class="line">        domain       = &quot;920oj.net&quot;</span><br><span class="line">        id           = &quot;xxxxxxxxxxxxxxxxxxxxxxx&quot;</span><br><span class="line">        project_name = &quot;920oj-net&quot;</span><br><span class="line">        status       = &quot;active&quot;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">cloudflare_pages_project.project-920oj-net will be imported</span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">(config will be generated)</span></span><br><span class="line">    resource &quot;cloudflare_pages_project&quot; &quot;project-920oj-net&quot; &#123;</span><br><span class="line">        account_id        = &quot;xxxxxxxxxxxxxxxxxxxxxxx&quot;</span><br><span class="line">        created_on        = &quot;2023-03-18T15:44:28Z&quot;</span><br><span class="line">        domains           = [</span><br><span class="line">            &quot;920oj-net.pages.dev&quot;,</span><br><span class="line">            &quot;920oj.net&quot;,</span><br><span class="line">        ]</span><br><span class="line">        id                = &quot;920oj-net&quot;</span><br><span class="line">        name              = &quot;920oj-net&quot;</span><br><span class="line">        production_branch = &quot;main&quot;</span><br><span class="line">        subdomain         = &quot;920oj-net.pages.dev&quot;</span><br><span class="line"></span><br><span class="line">        build_config &#123;</span><br><span class="line">            build_caching   = false</span><br><span class="line">            build_command   = &quot;npm run build&quot;</span><br><span class="line">            destination_dir = &quot;build&quot;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">・・・中略</span><br><span class="line"></span><br><span class="line">Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.</span><br></pre></td></tr></table></figure></div>

<p>設定値が読み取られ、出力されています。また、指定したファイル <code>generate.tf</code> に同様の設定値が記載されています！</p>
<p>内容が正しいか確認するのと、コメントを消したり、local変数に置き換えたりして体裁を整えましょう。また、ファイルもリソースごとに分けておきましょう。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-7dqq3k-13" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-13" title="コードの折り返しを切り替える"></label><figcaption><span>pages_domain.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;cloudflare_pages_domain&quot;</span> <span class="string">&quot;domain-920oj-net&quot;</span> {
  account_id   = local.account_id
  domain       = <span class="string">&quot;920oj.net&quot;</span>
  project_name = <span class="string">&quot;920oj-net&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-7dqq3k-14" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-14" title="コードの折り返しを切り替える"></label><figcaption><span>generate.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;cloudflare_pages_project&quot;</span> <span class="string">&quot;project-920oj-net&quot;</span> {
  account_id        = local.account_id
  name              = local.project_name
  production_branch = <span class="string">&quot;main&quot;</span>
  build_config {
    build_caching       = false
    build_command       = <span class="string">&quot;npm run build&quot;</span>
    destination_dir     = <span class="string">&quot;build&quot;</span>
    root_dir            = null
    web_analytics_tag   = null
    web_analytics_token = null
  }
  deployment_configs {
    preview {
      always_use_latest_compatibility_date = false
      compatibility_date                   = <span class="string">&quot;2023-03-18&quot;</span>
      compatibility_flags                  = []
      d1_databases                         = {}
      durable_object_namespaces            = {}
      environment_variables = {
        GTAG_ID      = <span class="string">&quot;XXXXXXX&quot;</span>
        NODE_VERSION = <span class="string">&quot;XXXXXXX&quot;</span>
      }
      fail_open     = true
      kv_namespaces = {}
      r2_buckets    = {}
      secrets       = null <span class="comment"># sensitive</span>
      usage_model   = <span class="string">&quot;standard&quot;</span>
    }
    production {
      always_use_latest_compatibility_date = false
      compatibility_date                   = <span class="string">&quot;2023-03-18&quot;</span>
      compatibility_flags                  = []
      d1_databases                         = {}
      durable_object_namespaces            = {}
      environment_variables = {
        GTAG_ID      = <span class="string">&quot;XXXXXXXXXX&quot;</span>
        NODE_VERSION = <span class="string">&quot;XXXXXXXXXX&quot;</span>
      }
      fail_open     = true
      kv_namespaces = {}
      r2_buckets    = {}
      secrets       = null <span class="comment"># sensitive</span>
      usage_model   = <span class="string">&quot;standard&quot;</span>
    }
  }
  source {
    type = <span class="string">&quot;github&quot;</span>
    config {
      deployments_enabled           = true
      owner                         = <span class="string">&quot;920oj&quot;</span>
      pr_comments_enabled           = true
      preview_branch_excludes       = []
      preview_branch_includes       = [<span class="string">&quot;*&quot;</span>]
      preview_deployment_setting    = <span class="string">&quot;all&quot;</span>
      production_branch             = <span class="string">&quot;main&quot;</span>
      production_deployment_enabled = true
      repo_name                     = <span class="string">&quot;920oj-net&quot;</span>
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<h4 id="importの実行">importの実行</h4><p>コードの記載は済んだので、tfstateへ取り込みましょう。</p>
<p>先ほどのimportブロックは残したままで、 <code>terraform plan</code> を実行します。 <code>Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.</code> が出ていれば、インポートの準備ができていることがわかります。</p>
<p>次に、 <code>terraform apply</code> を実行します。差分がないことを確認して、yesとタイプしましょう。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-7dqq3k-15" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-15" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.</span><br><span class="line"></span><br><span class="line">Do you want to perform these actions?</span><br><span class="line">  Terraform will perform the actions described above.</span><br><span class="line">  Only <span class="string">&#x27;yes&#x27;</span> will be accepted to approve.</span><br><span class="line"></span><br><span class="line">  Enter a value: <span class="built_in">yes</span></span><br></pre></td></tr></table></figure></div>

<p><code>Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.</code> が出たらOKです。</p>
<p>このあと <code>terraform plan</code> を実行してみて、差分が出ていなければ問題なしです。</p>
<p>先ほどインポートに利用したimport.tfは削除しても構いません。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-7dqq3k-16" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-7dqq3k-16" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"> % </span><span class="language-bash">terraform plan</span></span><br><span class="line">cloudflare_pages_domain.domain-920oj-net: Refreshing state... [id=xxxxxxxxxx]</span><br><span class="line">cloudflare_pages_project.project-920oj-net: Refreshing state... [id=xxxxxxxxxx]</span><br><span class="line"></span><br><span class="line">No changes. Your infrastructure matches the configuration.</span><br><span class="line"></span><br><span class="line">Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.</span><br></pre></td></tr></table></figure></div>

<p>これにて、Cloudflareで管理しているドメインのDNSレコードとCloudflare Pagesのリソースを、Terraformにて管理できるようになりました！</p>
<h2 id="おわりに">おわりに</h2><p>これでCloudflareを操作する際の不安が軽減できるようになり、自分の個人開発モチベも（わずかながら）高まった気がします。また、Terraformのエコシステムや本体の機能の充実さも改めて実感しました。</p>
<p>ぜひ皆さんもCloudflareをTerraform管理してみましょう！</p>
]]></content>
    <summary type="html">Cloudflareで管理しているドメインのDNS設定や、Cloudflare Pages等のサービスの設定を、Terraform管理に移行した際の手順等を、備忘録がてら記載します。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Cloudflare" scheme="https://future-architect.github.io/tags/Cloudflare/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="tfstate" scheme="https://future-architect.github.io/tags/tfstate/"/>
  </entry>
  <entry>
    <title>Terraformの実装コードを、動かしながら読む</title>
    <link href="https://future-architect.github.io/articles/20240326a/"/>
    <id>https://future-architect.github.io/articles/20240326a/</id>
    <published>2024-03-25T15:00:00.000Z</published>
    <updated>2024-03-25T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2024/20240326a/top.png" alt="" width="800" height="539">

<p>Terraform連載2024 の9本目記事です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは。CSIG（Cyber Security Innovation Group）の棚井です。</p>
<p>Terraform 連載ということで…</p>
<ul>
<li>そういえば、実装コードは Go で書かれていたな</li>
<li>コマンドの使い方はインフラエンジニアの皆様が書いてくれるはずなので、コードリーディングしようかな</li>
</ul>
<p>との考えに至り、ソースコードリーディング自体をブログ化しました。<br>参考になる点が1つでもあれば幸いです。</p>
<h2 id="エディタの準備">エディタの準備</h2><p>今回のコードリーディングでは VSCode を利用します。</p>
<p>Go のコードジャンプやテスト実行のため、以下の拡張機能を追加します。</p>
<ul>
<li>Go</li>
<li>Go Outliner</li>
<li>Go Test Explorer</li>
</ul>
<p>また、コードリーディングのお供として「GitHub Copilot」も追加します。<br>GitHub アカウントで Copilot を有効化する方法や、VSCode の拡張機能とリンクする方法については、ネット上に多数情報がありますのでそちらをご参照ください（ex. GitHub Copilot のドキュメント）</p>
<ul>
<li>GitHub Copilot</li>
<li>GitHub Copilot Chat</li>
</ul>
<p>「コードリーディングで生成系AIを使うの？」という疑問を持たれた方向けへの回答として、GitHub Copilotには「コード生成機能」以外に、「コードの説明機能」があります。</p>
<p>使い方としては、</p>
<ol>
<li>解説して欲しいコードをハイライトする</li>
<li><code>Ctrl + i</code> によりCopilotのポップアップを表示する</li>
<li><code>/explain</code>を入力する</li>
</ol>
<p>の3ステップで利用可能です。</p>
<p>VSCodeで表示されている実コードベースで解説してくれますので、途中に詰まる部分があったとしても、Copilotのサポートにより大抵は独力で解決可能です。OSSのコードリーディングでは、まさにこの解説機能が非常に便利だと感じています。</p>
<h2 id="実行環境の準備">実行環境の準備</h2><p>コードリーディング中には「実際に動かしてみないと、イメージがつきにくい処理」が見つかります。いざという時にローカル環境で動かせるように、Terraformのビルド、動作検証が可能な環境を準備します。</p>
<p>ソースコードはこちらの hashicorp&#x2F;terraform リポジトリに公開されています。<br>Goのバージョンを確認したところ、トップディレクトリ配下の <code>.go-version</code> に <code>1.22.1</code>（執筆時点）と記載されていることを確認しました。</p>
<p>執筆時点での Go All releases も <code>1.22.1</code> なので、最新のGoバージョンに対応していることが分かります。</p>
<blockquote>
<p>&#x3D;&#x3D; 宣伝 &#x3D;&#x3D;<br>FutureではGoリリース連載を実施しております。<br>Go 1.22リリース連載始まります<br>Goリリースノートから技術ブログを書く流れ基礎</p>
</blockquote>
<p>リポジトリ側でGoのバージョンが指定されているので、ローカル環境もそれに合わせて構築します。</p>
<p>ちなみに、私は asdf を利用して開発言語のバージョンを管理しています。asdf の利用方法は、左記の公式ドキュメント、および、こちらの解説記事（asdf で開発言語と利用ツールのバージョン管理）をご覧ください。</p>
<p><code>$ go version</code> 実行時に、<code>1.22.1</code> が表示されていれば、実行環境の準備は完了です。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go version</span></span><br><span class="line">go version go1.22.1 linux/amd64</span><br></pre></td></tr></table></figure>

<h2 id="リポジトリの取得、テスト実行">リポジトリの取得、テスト実行</h2><p>それではさっそく、Terraform のソースコードを取得していきます。<br>といっても、ここでは hashicorp&#x2F;terraform リポジトリをクローンするだけです。</p>
<p>クローンに成功したら、まずはテストに通過するかを確認します。</p>
<details>
<summary>テスト実行ログ</summary><div>

<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-17uw69i-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> terraform/</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go <span class="built_in">test</span> ./...</span></span><br><span class="line">?       github.com/hashicorp/terraform/internal/backend [no test files]</span><br><span class="line">ok      github.com/hashicorp/terraform  0.065s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/addrs   0.030s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/backend/backendbase     0.007s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/backend/backendrun      0.009s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/backend/init    0.051s</span><br><span class="line">?       github.com/hashicorp/terraform/internal/cloudplugin/cloudproto1 [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/cloudplugin/mock_cloudproto1    [no test files]</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/backend/local   6.211s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/backend/remote  4.009s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/backend/remote-state/http       7.266s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/backend/remote-state/inmem      0.010s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/builtin/providers/terraform     0.150s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/builtin/provisioners/file       0.008s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/builtin/provisioners/local-exec 0.196s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/builtin/provisioners/remote-exec        2.050s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/checks  0.060s</span><br><span class="line">?       github.com/hashicorp/terraform/internal/command/jsonformat/collections  [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/command/jsonformat/computed     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/command/jsonformat/jsondiff     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/command/jsonformat/structured   [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/command/testing [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/e2e     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/experiments     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/getmodules      [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/grpcwrap        [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/lang/langrefs   [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/lang/marks      [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/lang/types      [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/modsdir [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/plans/planproto [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/plugin/mock_proto       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/plugin6/mock_proto      [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/provider-simple [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/provider-simple/main    [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/provider-simple-v6      [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/provider-simple-v6/main [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/provider-terraform/main [no test files]</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/cloud   36.011s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/cloud/cloudplan 0.111s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/cloud/e2e       8.538s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/cloudplugin     0.239s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/cloudplugin/cloudplugin1        0.029s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/collections     0.013s</span><br><span class="line">?       github.com/hashicorp/terraform/internal/providers/testing       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/provisioner-local-exec/main     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/provisioners    [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/registry/test   [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/replacefile     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/rpcapi/dynrpcserver     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/rpcapi/dynrpcserver/generator   [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/rpcapi/terraform1       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/stacks/stackconfig/stackconfigtypes     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/stacks/stackconfig/typeexpr     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/schemarepo/loadschemas  [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/schemarepo      [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/stacks/stackaddrs       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/stacks/stackruntime/hooks       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/stacks/stackruntime/testing     [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/stacks/stackutils       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/tfplugin5       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/internal/tfplugin6       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/tools/loggraphdiff       [no test files]</span><br><span class="line">?       github.com/hashicorp/terraform/tools/protobuf-compile   [no test files]</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command 88.238s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/arguments       0.014s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/cliconfig       0.027s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/clistate        0.010s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/e2etest 30.692s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/format  0.010s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonchecks      0.009s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonconfig      0.013s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonformat      0.111s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers   0.035s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonformat/differ       0.058s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonformat/structured/attribute_path    0.014s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonfunction    0.019s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonplan        0.028s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonprovider    0.024s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/jsonstate       0.036s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/views   3.473s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/views/json      0.053s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/webbrowser      0.016s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/command/workdir 0.029s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/communicator    1.049s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/communicator/remote     0.010s [no tests to run]</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/communicator/shared     0.005s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/communicator/ssh        3.183s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/communicator/winrm      0.045s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/configs 7.155s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/configs/configload      0.888s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/configs/configschema    0.038s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/configs/configtesting   0.004s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/configs/hcl2shim        0.036s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/copy    0.012s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/dag     2.168s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/depsfile        0.214s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/didyoumean      0.012s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/genconfig       0.051s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/getmodules/moduleaddrs  0.063s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/getproviders    4.428s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/getproviders/providerreqs       0.101s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/helper/slowmessage      0.105s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/httpclient      0.020s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/initwd  0.312s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/instances       0.019s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/ipaddr  0.026s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/lang    0.250s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/lang/blocktoattr        0.016s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/lang/funcs      0.792s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/lang/globalref  0.326s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/logging 0.004s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/moduledeps      0.021s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/moduletest      0.203s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/moduletest/config       0.028s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/moduletest/hcl  0.045s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/moduletest/mocking      0.025s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/namedvals       0.010s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plans   0.018s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plans/deferring 0.010s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plans/objchange 0.115s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plans/planfile  0.331s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plugin  0.040s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plugin/convert  0.027s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plugin/discovery        0.013s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plugin6 0.028s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/plugin6/convert 0.022s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/promising       0.041s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/providercache   0.232s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/providers       0.012s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/refactoring     0.261s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/registry        3.826s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/registry/regsrc 0.008s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/registry/response       0.010s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/releaseauth     0.167s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/repl    0.096s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/rpcapi  0.263s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/stacks/stackconfig      0.017s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/stacks/stackplan        0.029s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/stacks/stackruntime     0.484s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/stacks/stackruntime/internal/stackeval  1.489s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/stacks/stackstate       0.022s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/stacks/stackstate/statekeys     0.028s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/stacks/tfstackdata1     0.017s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/states  0.018s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/states/remote   0.032s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/states/statefile        0.070s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/states/statemgr 5.951s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/terminal        0.005s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/terraform       8.988s</span><br><span class="line">ok      github.com/hashicorp/terraform/internal/tfdiags 0.012s</span><br><span class="line">ok      github.com/hashicorp/terraform/version  0.003s</span><br></pre></td></tr></table></figure></div>

</div></details>

<p>テストを実行してみたところ、<code>[no test files]</code> が多数見つかりました。<br>少し気になりますので、テストのカバレッジを見てみます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-17uw69i-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go <span class="built_in">test</span> -cover</span></span><br><span class="line">Terraform has no command named &quot;bar&quot;.</span><br><span class="line"></span><br><span class="line">To see all of Terraform&#x27;s top-level commands, run:</span><br><span class="line">  terraform -help</span><br><span class="line"></span><br><span class="line">PASS</span><br><span class="line">coverage: 36.7% of statements</span><br><span class="line">ok      github.com/hashicorp/terraform  0.049s</span><br></pre></td></tr></table></figure></div>

<p>上記ログには…</p>
<blockquote>
<p>coverage: 36.7% of statements</p>
</blockquote>
<p>とありますので、Terraform 実装コードのテストカバレッジ率は <code>36.7%</code> です。<br>数字の是非はさておいて、テストが通過することは確認できました。</p>
<h2 id="ビルドして動かしてみる">ビルドして動かしてみる</h2><p><code>terraform</code> がコマンドの1つである以上、「ビルドして動かせる」はずなので、実際に試してみます。</p>
<p>Go言語では王道の Makefile を見たところ、<code>go build</code> に相当しそうなコマンドは見つかりません。<br>ただし、いくつかのコマンドが <code>$(CURDIR)/scripts/</code> 配下のシェルスクリプトを参照していますので、当該ディレクトリにお目当てのファイルがないかを確認します。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-17uw69i-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l scripts/</span></span><br><span class="line">total 40</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user 2853 Mar 25 05:19 build.sh</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user  958 Mar 24 22:52 changelog-links.sh</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user  610 Mar 24 22:52 copyright.sh</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user 1171 Mar 24 22:52 debug-terraform</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user  344 Mar 24 22:52 exhaustive.sh</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user  401 Mar 24 22:52 gofmtcheck.sh</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user  351 Mar 24 22:52 gogetcookie.sh</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user 2730 Mar 24 22:52 goimportscheck.sh</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user  666 Mar 24 22:52 staticcheck.sh</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user 1096 Mar 24 22:52 syncdeps.sh</span><br></pre></td></tr></table></figure></div>

<p><code>scripts/</code> 配下に、<code>build.sh</code> というシェルスクリプトが見つかりました。<br>また、VSCode で <code>Ctrl + Shift + f</code> を実行して <code>build.sh</code> を検索すると、Dockerfile の中でこのシェルスクリプトが呼ばれていることも確認できます。</p>
<p>シェル冒頭に以下の記載があり、<code>bash</code> によリコールされた後、1つ上のディレクトリでビルドプロセスを動かしていることが分かります。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-17uw69i-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-4" title="コードの折り返しを切り替える"></label><figcaption><span>build.sh</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Get the parent directory of where this script is.</span></span><br><span class="line">SOURCE=<span class="string">&quot;<span class="variable">$&#123;BASH_SOURCE[0]&#125;</span>&quot;</span></span><br><span class="line"><span class="keyword">while</span> [ -h <span class="string">&quot;<span class="variable">$SOURCE</span>&quot;</span> ] ; <span class="keyword">do</span> SOURCE=<span class="string">&quot;<span class="subst">$(readlink <span class="string">&quot;<span class="variable">$SOURCE</span>&quot;</span>)</span>&quot;</span>; <span class="keyword">done</span></span><br><span class="line">DIR=<span class="string">&quot;<span class="subst">$( cd -P <span class="string">&quot;<span class="subst">$( dirname <span class="string">&quot;<span class="variable">$SOURCE</span>&quot;</span> )</span>/..&quot;</span> &amp;&amp; pwd )</span>&quot;</span></span><br></pre></td></tr></table></figure></div>

<p>上記を踏まえて、さっそくビルドしてみます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-17uw69i-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">/usr/bin/bash build.sh</span></span><br><span class="line">==&gt; Removing old directory...</span><br><span class="line">==&gt; Installing gox...</span><br><span class="line">==&gt; Building...</span><br><span class="line">Number of parallel builds: 11</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">  solaris/amd64: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">  windows/amd64: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">    freebsd/arm: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">    openbsd/386: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">  openbsd/amd64: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">      linux/arm: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">      linux/386: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">    freebsd/386: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">    linux/amd64: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">  freebsd/amd64: github.com/hashicorp/terraform</span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">    windows/386: github.com/hashicorp/terraform</span></span><br></pre></td></tr></table></figure></div>

<p>計11個のビルドプロセスが並列で動いています。</p>
<p>このまましばらく放置していれば、11環境分すべての <code>terraform</code> 実行バイナリが作成されるのですが、私のPC環境では以下の問題が発生しました。</p>
<img src="/images/2024/20240326a/disc.png" alt="disc.png" width="544" height="170" loading="lazy">

<p>リポジトリからクローンしたソースコード全体と、ビルドで生成した実行バイナリのダブルパンチにより、ローカル PC が悲鳴を上げていました。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">du</span> -h terraform/bin/terraform</span></span><br><span class="line">119M    terraform</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">du</span> -h terraform/</span></span><br><span class="line">...</span><br><span class="line">820M    terraform/</span><br></pre></td></tr></table></figure>

<p>何とかならないか？ と <code>build.sh</code> を読み進めたところ、環境変数 <code>TF_DEV</code> に値を設定すれば、ビルド環境だけの実行バイナリを生成してくれるとありました。</p>
<figure class="highlight sh"><figcaption><span>build.sh</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># If its dev mode, only build for ourself</span></span><br><span class="line"><span class="keyword">if</span> [[ -n <span class="string">&quot;<span class="variable">$&#123;TF_DEV&#125;</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">    XC_OS=$(go <span class="built_in">env</span> GOOS)</span><br><span class="line">    XC_ARCH=$(go <span class="built_in">env</span> GOARCH)</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure>

<p>また、<code>TF_DEV</code> を設定しない場合には、ビルドした実行バイナリのパッケージ化が行われるとの記載も見つかりました。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-17uw69i-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-6" title="コードの折り返しを切り替える"></label><figcaption><span>build.sh</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$&#123;TF_DEV&#125;</span>x&quot;</span> = <span class="string">&quot;x&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="comment"># Zip and copy to the dist dir</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;==&gt; Packaging...&quot;</span></span><br><span class="line">    <span class="keyword">for</span> PLATFORM <span class="keyword">in</span> $(find ./pkg -mindepth 1 -maxdepth 1 -<span class="built_in">type</span> d); <span class="keyword">do</span></span><br><span class="line">        OSARCH=$(<span class="built_in">basename</span> <span class="variable">$&#123;PLATFORM&#125;</span>)</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;--&gt; <span class="variable">$&#123;OSARCH&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="built_in">pushd</span> <span class="variable">$PLATFORM</span> &gt;/dev/null 2&gt;&amp;1</span><br><span class="line">        zip ../<span class="variable">$&#123;OSARCH&#125;</span>.zip ./*</span><br><span class="line">        <span class="built_in">popd</span> &gt;/dev/null 2&gt;&amp;1</span><br><span class="line">    <span class="keyword">done</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure></div>

<p>今回はローカル環境でのみビルド、動作検証ができれば十分ですので、環境変数 <code>TF_DEV</code> を設定し再ビルドします。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-17uw69i-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">export</span> TF_DEV=<span class="built_in">yes</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> <span class="variable">$TF_DEV</span></span></span><br><span class="line">yes</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go <span class="built_in">env</span> GOOS</span></span><br><span class="line">linux</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go <span class="built_in">env</span> GOARCH</span></span><br><span class="line">amd64</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">/usr/bin/bash build.sh</span></span><br><span class="line">==&gt; Removing old directory...</span><br><span class="line">==&gt; Building...</span><br><span class="line">Number of parallel builds: 11</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">--&gt; </span><span class="language-bash">    linux/amd64: github.com/hashicorp/terraform</span></span><br><span class="line">==&gt; Creating GOPATH/bin directory...</span><br><span class="line"></span><br><span class="line">==&gt; Results:</span><br><span class="line">total 119M</span><br><span class="line">-rwxr-xr-x 1 blog-user blog-user 119M Mar 25 03:45 terraform</span><br></pre></td></tr></table></figure></div>

<p>無事に、<code>linux/amd64</code> 分のビルドに成功しました。<br>実行バイナリは、以下2つのディレクトリに出力されています。</p>
<ul>
<li>terraform&#x2F;bin</li>
<li>GOPATH&#x2F;bin</li>
</ul>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">./terraform/bin/terraform version</span></span><br><span class="line">Terraform v1.9.0-dev</span><br><span class="line">on linux_amd64</span><br></pre></td></tr></table></figure>

<p>ここまでの操作により、ソースコードのビルドから、コマンドの実行手順まで確認できました。</p>
<p>続いて、コードに手を加えた場合には、ビルド後のコマンド中身に反映されていることを検証してみます。</p>
<p>サブコマンドの <code>version</code> が分かりやすいので、以下のログを追加します。<br>（対応箇所）</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-17uw69i-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-8" title="コードの折り返しを切り替える"></label><figcaption><span>version.go</span></figcaption><table><tr><td class="code"><pre><span class="line">...</span><br><span class="line">fmt.Fprintf(&amp;versionString, <span class="string">&quot;Terraform v%s&quot;</span>, c.Version)</span><br><span class="line"><span class="keyword">if</span> c.VersionPrerelease != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line"> fmt.Fprintf(&amp;versionString, <span class="string">&quot;-%s&quot;</span>, c.VersionPrerelease)</span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure></div>

<p>↓</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-17uw69i-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-9" title="コードの折り返しを切り替える"></label><figcaption><span>version.go</span></figcaption><table><tr><td class="code"><pre><span class="line">...</span><br><span class="line">fmt.Println(<span class="string">&quot;バージョン確認コマンドを実行してみた。&quot;</span>) <span class="comment">// 追加</span></span><br><span class="line">fmt.Fprintf(&amp;versionString, <span class="string">&quot;Terraform v%s&quot;</span>, c.Version)</span><br><span class="line"><span class="keyword">if</span> c.VersionPrerelease != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line"> fmt.Fprintf(&amp;versionString, <span class="string">&quot;-%s&quot;</span>, c.VersionPrerelease)</span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure></div>

<p>この1行が追記された状態で実行バイナリをビルドすると、コマンドのログ出力が増えていることを確認できます。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">/usr/bin/bash ./terraform/scripts/build.sh</span></span><br><span class="line">...</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">./terraform/bin/terraform version</span></span><br><span class="line">バージョン確認コマンドを実行してみた。</span><br><span class="line">Terraform v1.9.0-dev</span><br><span class="line">on linux_amd64</span><br></pre></td></tr></table></figure>

<h2 id="エントリーポイントから見ていく">エントリーポイントから見ていく</h2><p>ここまでが事前準備です。<br>さっそく、Terraform の実コードを見ていきます。</p>
<p>まずはプログラムの始まりとなる「エントリーポイント」を探します。</p>
<p>Go であれば…</p>
<ul>
<li>main.go</li>
<li>func main() {…}</li>
</ul>
<p>がプログラムのエントリーポイントです。<br>トップディレクトリ配下の「terraform&#x2F;main.go」に以下の記述が見つかりました。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"> os.Exit(realMain())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>初手、<code>main()</code> の中で <code>realMain()</code>（直訳すると「本当のmain」）を呼び出しているようです。<br>呼び出し先の関数を見ると、今度は defer で <code>logging.PanicHandler()</code> を呼び出しているようなので、この関数の中身を見てみます。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">realMain</span><span class="params">()</span></span> <span class="type">int</span> &#123;</span><br><span class="line"> <span class="keyword">defer</span> logging.PanicHandler()</span><br><span class="line"> ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>VSCode のコードジャンプが有効となっていれば、Ctrl を押しながら対象関数を左クリックすることにより、関数の定義元にジャンプできます。</p>
<h3 id="PanicHandler">PanicHandler()</h3><p>「PanicHandler()の実装」を見ますと、<strong>TERRAFORM CRASH</strong> という仰々しい言葉が沢山の <code>!</code> で囲まれていることが分かります。<br>通常のインフラ構築、運用保守作業にてこのようなメッセージをお目にかかることは、まずないと思います。私は今回、初めてこんなメッセージが仕込まれていることを知りました。</p>
<p>メッセージ内容を日本語訳しますと「Terraform が壊れたよ！ 公式リポジトリの issue に記票して」とありますので、さっそく、壊してみます。</p>
<p>panic 発生時に <strong>TERRAFORM CRASH</strong> が表示されるようなので、エントリーポイントの直後で強制的に panic を起こす1行を入れます。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">realMain</span><span class="params">()</span></span> <span class="type">int</span> &#123;</span><br><span class="line"> <span class="keyword">defer</span> logging.PanicHandler()</span><br><span class="line"> <span class="built_in">panic</span>(<span class="string">&quot;バルス！&quot;</span>)</span><br><span class="line"> ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>また、<code>terraform</code> のビルド時に渡される <code>GOFLAGS</code> を「リポジトリのコード」のまま利用した場合、ビルド環境のフルパスが表示されてしまいますので、以下の <code>-trimpath</code> フラグを追加します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> GOFLAGS=<span class="string">&quot;-mod=readonly&quot;</span></span><br></pre></td></tr></table></figure>

<p>↓</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> GOFLAGS=<span class="string">&quot;-mod=readonly -trimpath&quot;</span></span><br></pre></td></tr></table></figure>

<p>この状態でソースコードのビルド、及び、コマンドの実行を試してみます。</p>
<p>サブコマンドを与えずに <code>terraform</code> を実行した場合、本来ならば <code>help</code> が表示されますが、無事に「壊す」ことができました。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-17uw69i-10" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">./terraform</span></span><br><span class="line"></span><br><span class="line">!!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!</span><br><span class="line"></span><br><span class="line">Terraform crashed! This is always indicative of a bug within Terraform.</span><br><span class="line">Please report the crash with Terraform[1] so that we can fix this.</span><br><span class="line"></span><br><span class="line">When reporting bugs, please include your terraform version, the stack trace</span><br><span class="line">shown below, and any additional information which may help replicate the issue.</span><br><span class="line"></span><br><span class="line">[1]: https://github.com/hashicorp/terraform/issues</span><br><span class="line"></span><br><span class="line">!!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!</span><br><span class="line"></span><br><span class="line">panic: バルス！</span><br><span class="line">goroutine 1 [running]:</span><br><span class="line">runtime/debug.Stack()</span><br><span class="line">        runtime/debug/stack.go:24 +0x5e</span><br><span class="line">github.com/hashicorp/terraform/internal/logging.PanicHandler()</span><br><span class="line">        github.com/hashicorp/terraform/internal/logging/panic.go:84 +0x18b</span><br><span class="line">panic(&#123;0x1ade400?, 0x249fc00?&#125;)</span><br><span class="line">        runtime/panic.go:770 +0x132</span><br><span class="line">main.realMain()</span><br><span class="line">        github.com/hashicorp/terraform/main.go:69 +0x47</span><br><span class="line">main.main()</span><br><span class="line">        github.com/hashicorp/terraform/main.go:64 +0x13</span><br></pre></td></tr></table></figure></div>

<p><code>terraform</code> コマンドの実行時、何らかの理由により panic が起きてしまった場合には、<strong>TERRAFORM CRASH</strong> のメッセージ表示と issue の起票催促、デバッグトレースが表示されることを確認できました。</p>
<p>それでは、意図的に仕込んだ panic の1行を削除して、再ビルドまで完了したら、次の処理を見ていきます。</p>
<h3 id="openTelemetryInit">openTelemetryInit()</h3><p>次の実装として、Open Telemetry を扱う処理が見つかります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-17uw69i-11" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-11" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> err <span class="type">error</span></span><br><span class="line"></span><br><span class="line">err = openTelemetryInit()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">  <span class="comment">// openTelemetryInit can only fail if Terraform was run with an</span></span><br><span class="line">  <span class="comment">// explicit environment variable to enable telemetry collection,</span></span><br><span class="line">  <span class="comment">// so in typical use we cannot get here.</span></span><br><span class="line">  Ui.Error(fmt.Sprintf(<span class="string">&quot;Could not initialize telemetry: %s&quot;</span>, err)) </span><br><span class="line">  Ui.Error(fmt.Sprintf(<span class="string">&quot;Unset environment variable %s if you don&#x27;t intend to collect telemetry from Terraform.&quot;</span>, openTelemetryExporterEnvVar))</span><br><span class="line">  <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> ctx context.Context</span><br><span class="line"><span class="keyword">var</span> otelSpan trace.Span</span><br><span class="line">&#123;</span><br><span class="line">  <span class="comment">// At minimum we emit a span covering the entire command execution.</span></span><br><span class="line">  _, displayArgs := shquot.POSIXShellSplit(os.Args)</span><br><span class="line">  ctx, otelSpan = tracer.Start(context.Background(), fmt.Sprintf(<span class="string">&quot;terraform %s&quot;</span>, displayArgs))</span><br><span class="line">  <span class="keyword">defer</span> otelSpan.End()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>openTelemetryInit()</code> の定義元にコードジャンプしますと、トップディレクトリ配下の「terraform&#x2F;telemetry.go」にて詳細内容が説明されています。</p>
<p>まず、実装コードのコメントには以下の記載があります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-17uw69i-12" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-12" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// If this environment variable is set to &quot;otlp&quot; when running Terraform CLI</span></span><br><span class="line"><span class="comment">// then we&#x27;ll enable an experimental OTLP trace exporter.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// BEWARE! This is not a committed external interface.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Everything about this is experimental and subject to change in future</span></span><br><span class="line"><span class="comment">// releases. Do not depend on anything about the structure of this output.</span></span><br><span class="line"><span class="comment">// This mechanism might be removed altogether if a different strategy seems</span></span><br><span class="line"><span class="comment">// better based on experience with this experiment.</span></span><br><span class="line"><span class="keyword">const</span> openTelemetryExporterEnvVar = <span class="string">&quot;OTEL_TRACES_EXPORTER&quot;</span></span><br></pre></td></tr></table></figure></div>

<p>Terraform の実行環境にて、環境変数として以下を設定した場合のみ、Open Telemetry 機能が有効となるようです。<br><code>otlp</code> 以外の値（値ナシも含む）が設定された場合には、この機能は有効化されずに関数の呼び出し元へ戻ります。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> OTEL_TRACES_EXPORTER=otlp</span><br></pre></td></tr></table></figure>

<p>コメントには「OpenTelemetry Protocol Exporter Configuration Options」へのリンクが添付されています。</p>
<p>ただし、Open Telemetry について本記事では立ち入りません。<br>気になる方は、以下の公式ドキュメント・日本語記事・翻訳書籍をご参照ください。</p>
<ul>
<li>公式ドキュメント<ul>
<li>What is OpenTelemetry?</li>
</ul>
</li>
<li>日本語記事<ul>
<li>OpenTelemetryに触れてみた</li>
</ul>
</li>
<li>翻訳書籍<ul>
<li>オブザーバビリティ・エンジニアリング</li>
</ul>
</li>
</ul>
<p>ここでは、実装コードを参照する中で、Terraform には「環境変数の <code>OTEL_TRACES_EXPORTER</code> に <code>otlp</code> を与えることで、Open Telemetry が有効化される」ことが分かりました。</p>
<p>このような知識はもちろん公式ドキュメントを漁れば見つかるのだとは思いますが、自分で探索して見つけたときの「自力で発見できた感覚」を味わえるのが、OSS コードリーディングの面白さだと私は感じております。少々、蛇足に過ぎましたので、元のコードに戻ります。</p>
<h3 id="tmpLogPath">tmpLogPath</h3><p>続いて、<code>tmpLogPath</code> という「一時的なログファイルの出力先」になりそうな変数が見つかりました。<br>適切な変数名は「適切なメンタルモデル」を脳内に作るために重要なので、こういった側面においても、OSS のコードリーディングでは書籍からは得られない実践知が詰っていると感じます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-17uw69i-13" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-13" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">tmpLogPath := os.Getenv(envTmpLogPath)</span><br><span class="line"><span class="keyword">if</span> tmpLogPath != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">  f, err := os.OpenFile(tmpLogPath, os.O_RDWR|os.O_APPEND, <span class="number">0666</span>)</span><br><span class="line">  <span class="keyword">if</span> err == <span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="keyword">defer</span> f.Close()</span><br><span class="line"></span><br><span class="line">    log.Printf(<span class="string">&quot;[DEBUG] Adding temp file log sink: %s&quot;</span>, f.Name())</span><br><span class="line">    logging.RegisterSink(f)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    log.Printf(<span class="string">&quot;[ERROR] Could not open temp log file: %v&quot;</span>, err)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>ここの実装では、環境変数の <code>TF_TEMP_LOG_PATH</code> で指定したファイルに、ログを追記する処理が定義されています。<br>それでは、ログの出力先を指定して、出力ログと実装コードの対応を確認していきます。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># ログの出力先を /tmp/tf.log に指定</span></span><br><span class="line"><span class="built_in">export</span> TF_TEMP_LOG_PATH=<span class="string">&quot;/tmp/tf.log&quot;</span></span><br></pre></td></tr></table></figure>

<p>ログ出力先の環境変数を設定してから任意の <code>terraform</code> コマンドを実行すると、ファイルには以下のログが追記されます。</p>
<div class="code-block"><figure class="highlight plaintext"><input type="checkbox" id="code-wrap-17uw69i-14" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-14" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">2024-03-25T05:07:16.800+0900 [INFO]  Terraform version: 1.9.0 dev</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] using github.com/hashicorp/go-tfe v1.41.0</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] using github.com/hashicorp/hcl/v2 v2.20.0</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] using github.com/hashicorp/terraform-svchost v0.1.1</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] using github.com/zclconf/go-cty v1.14.3</span><br><span class="line">2024-03-25T05:07:16.800+0900 [INFO]  Go runtime version: go1.22.1</span><br><span class="line">2024-03-25T05:07:16.800+0900 [INFO]  CLI args: []string&#123;&quot;./terraform&quot;, &quot;version&quot;&#125;</span><br><span class="line">2024-03-25T05:07:16.800+0900 [TRACE] Stdout is a terminal of width 125</span><br><span class="line">2024-03-25T05:07:16.800+0900 [TRACE] Stderr is a terminal of width 125</span><br><span class="line">2024-03-25T05:07:16.800+0900 [TRACE] Stdin is a terminal</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] Attempting to open CLI config file: /home/blog-user/.terraformrc</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] File doesn&#x27;t exist, but doesn&#x27;t need to. Ignoring.</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] ignoring non-existing provider search directory terraform.d/plugins</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] ignoring non-existing provider search directory /home/blog-user/.terraform.d/plugins</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] ignoring non-existing provider search directory /home/blog-user/.local/share/terraform/plugins</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] ignoring non-existing provider search directory /usr/local/share/terraform/plugins</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] ignoring non-existing provider search directory /usr/share/terraform/plugins</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] ignoring non-existing provider search directory /var/lib/snapd/desktop/terraform/plugins</span><br><span class="line">2024-03-25T05:07:16.801+0900 [INFO]  CLI command args: []string&#123;&quot;version&quot;&#125;</span><br></pre></td></tr></table></figure></div>

<p>これらのログと実装コードの対応を見ますと、<code>TF_TEMP_LOG_PATH</code> を設定した「直後の処理内容」が、そのままログとして格納されていることが分かります。</p>
<p>例えば、<code>version.InterestingDependencies()</code> により取得された「依存モジュールのバージョン情報」は、以下のように記録されています。</p>
<div class="code-block"><figure class="highlight plaintext"><input type="checkbox" id="code-wrap-17uw69i-15" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-15" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] using github.com/hashicorp/go-tfe v1.41.0</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] using github.com/hashicorp/hcl/v2 v2.20.0</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] using github.com/hashicorp/terraform-svchost v0.1.1</span><br><span class="line">2024-03-25T05:07:16.800+0900 [DEBUG] using github.com/zclconf/go-cty v1.14.3</span><br></pre></td></tr></table></figure></div>

<p>ログの対応を1つ1つ、実装コードと突き合わせてみますと、以下に対応するログが出力されていないことが分かります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-17uw69i-16" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-16" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> ExperimentsAllowed() &#123;</span><br><span class="line">  log.Printf(<span class="string">&quot;[INFO] This build of Terraform allows using experimental features&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>ExperimentsAllowed()</code> の定義にコードジャンプすると、「terraform&#x2F;experiments.go」のコメントとして、この関数を有効化する方法（返り値がtrueにする方法）が記載されています。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-17uw69i-17" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-17" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// experimentsAllowed can be set to any non-empty string using Go linker</span></span><br><span class="line"><span class="comment">// arguments in order to enable the use of experimental features for a</span></span><br><span class="line"><span class="comment">// particular Terraform build:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//   go install -ldflags=&quot;-X &#x27;main.experimentsAllowed=yes&#x27;&quot;</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// By default this variable is initialized as empty, in which case</span></span><br><span class="line"><span class="comment">// experimental features are not available.</span></span><br></pre></td></tr></table></figure></div>

<p>コメントの内容に従うと、<code>terraform</code> のビルド時に…</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-17uw69i-18" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-18" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">go install -ldflags=<span class="string">&quot;-X &#x27;main.experimentsAllowed=yes&#x27;&quot;</span></span><br></pre></td></tr></table></figure></div>

<p>を混ぜ込むことにより、<code>experiments</code>（実験的機能）を有効化できるようです。</p>
<p>「scripts&#x2F;build.sh」を確認すると、<code>-ldflags</code> に渡される値は以下のように定義されていることが分かります。<code>TF_RELEASE</code> に値を設定した場合のみ、<code>gox -ldflags &quot;&quot;$&#123;LD_FLAGS&#125;&quot;&quot;</code> が「ビルド時に追加」されています。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-17uw69i-19" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-19" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># In release mode we don&#x27;t want debug information in the binary and we don&#x27;t</span></span><br><span class="line"><span class="comment"># want the -dev version marker</span></span><br><span class="line"><span class="keyword">if</span> [[ -n <span class="string">&quot;<span class="variable">$&#123;TF_RELEASE&#125;</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">    LD_FLAGS=<span class="string">&quot;-s -w -X &#x27;github.com/hashicorp/terraform/version.dev=no&#x27;&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure></div>

<p>今回は <code>TF_RELEASE</code> を利用しないので、以下の分岐を追加します。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-17uw69i-20" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-20" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> [[ -n <span class="string">&quot;<span class="variable">$&#123;TF_RELEASE&#125;</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">    LD_FLAGS=<span class="string">&quot;-s -w -X &#x27;github.com/hashicorp/terraform/version.dev=no&#x27;&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    LD_FLAGS=<span class="string">&quot;-X &#x27;main.experimentsAllowed=yes&#x27;&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure></div>

<p>この状態で実行バイナリをビルドし、任意の <code>terraform</code> コマンドを実行すると、ログファイルに以下の1文が追記されることを確認できます。</p>
<div class="code-block"><figure class="highlight plaintext"><input type="checkbox" id="code-wrap-17uw69i-21" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-21" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">...</span><br><span class="line">[INFO]  This build of Terraform allows using experimental features</span><br><span class="line">...</span><br></pre></td></tr></table></figure></div>

<p><code>experiments</code> を有効化することにより、何かしらの実験的コマンドが利用可能となったのだと思います。しかし、ここまでのコードリーディングの範囲では「どのような機能が有効化されたのか？」についての情報に遭遇していないため、<code>ExperimentsAllowed()</code> の探索はここまでとします。</p>
<h3 id="terminal-Init">terminal.Init()</h3><p>コードリーディングとしては、ログ後半に注目してみます。<br>ここでは、ターミナルを初期化しているような <code>terminal.Init()</code> という関数と、その関数の返り値を利用して…</p>
<ul>
<li>標準入力</li>
<li>標準出力</li>
<li>標準エラー出力</li>
</ul>
<p>のそれぞれに対して、<code>IsXXX()</code> の方式で「ターミナルであるか、否か」を判断している以下の実装が見つかります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-17uw69i-22" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-17uw69i-22" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">streams, err := terminal.Init()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">  Ui.Error(fmt.Sprintf(<span class="string">&quot;Failed to configure the terminal: %s&quot;</span>, err))</span><br><span class="line">  <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> streams.Stdout.IsTerminal() &#123;</span><br><span class="line">  log.Printf(<span class="string">&quot;[TRACE] Stdout is a terminal of width %d&quot;</span>, streams.Stdout.Columns())</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  log.Printf(<span class="string">&quot;[TRACE] Stdout is not a terminal&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> streams.Stderr.IsTerminal() &#123;</span><br><span class="line">  log.Printf(<span class="string">&quot;[TRACE] Stderr is a terminal of width %d&quot;</span>, streams.Stderr.Columns())</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  log.Printf(<span class="string">&quot;[TRACE] Stderr is not a terminal&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> streams.Stdin.IsTerminal() &#123;</span><br><span class="line">  log.Printf(<span class="string">&quot;[TRACE] Stdin is a terminal&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  log.Printf(<span class="string">&quot;[TRACE] Stdin is not a terminal&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>terminal.Init()</code> の定義元にコードジャンプすると、こちらも「長文コメント」で実装内容、実装意図が説明されています。</p>
<p>コマンドの実行環境は <code>terraform</code> が正しく入力・出力を扱える環境なのか、この terminal パッケージ内にて確認処理を行っています。普段、ツールやコマンドを取得する際には <code>Requirements</code> を確認してインストールしますが、コマンドの実行プロセス内においても、実行環境を確認していることが確認できました。</p>
<p>…</p>
<p>本ブログでのコードリーディングは一旦ここまでとします。<br><code>func main() &#123;...&#125;</code> から読み始め、進捗行数としては70行程度です。ただし、途中でコードジャンプやシェルスクリプトの確認が入ったため、単純に「<code>main()</code> からの進捗行数 &#x3D; コードリーディング行数」というカウントにはなりません。「実際に動かしながらのコードリーディング」のスピードを実感いただけたでしょうか。</p>
<h2 id="おわりに">おわりに</h2><p>本ブログでは、「Terraform の実装コードを、動かしながら読む」という目標を掲げ、実行バイナリのビルドやコードの改造を取り入れながら、OSS のコードリーディングを行いました。前半の環境準備に原稿の多くが割かれているため、実際のコードリーディング行数は100行未満ではないかと思います。OSS コードリーディングの面白さは「各自が、自分の好き勝手に読めること」にあると思いますので、私ならどのように読むか？ を詳しく解説してきました。</p>
<p>ここまで長文にお付き合いいただき、ありがとうございました。</p>
]]></content>
    <summary type="html">Terraform 連載ということで、そういえば、実装コードは Go で書かれていたな、コマンドの使い方はインフラエンジニアの皆様が書いてくれるはずなので、コードリーディングしようかな</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Copilot" scheme="https://future-architect.github.io/tags/Copilot/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="コードリーディング" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0/"/>
  </entry>
  <entry>
    <title>Azure環境Terraform実行におけるリソースプロバイダーについて</title>
    <link href="https://future-architect.github.io/articles/20240325a/"/>
    <id>https://future-architect.github.io/articles/20240325a/</id>
    <published>2024-03-24T15:00:00.000Z</published>
    <updated>2024-03-24T15:00:00.000Z</updated>
    <author><name>真鍋優</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2024/20240325a/top.png" alt="" width="800" height="515">

<p>Terraform連載2024を の8本目です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは、SAIG(Strategic AI Group)の真鍋です。</p>
<p>昨今のOpenAI需要によって、Azure環境の利用を本格的に始める方も多いかと思います。</p>
<p>Azure環境でTerraformを利用する際、裏で動いているリソースプロバイダーについてご紹介します。</p>
<h2 id="Terraform実行時に踏んだエラー">Terraform実行時に踏んだエラー</h2><p>利用中のAzure環境にて<code>terraform plan</code>を実行した際に、下記エラーが発生しました。</p>
<p>見やすいように改行等追加しています。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-87pkpw-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-87pkpw-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Original Error: Cannot register providers:</span><br><span class="line">Microsoft.ServiceBus,</span><br><span class="line">Microsoft.DBforPostgreSQL,</span><br><span class="line">(中略)...</span><br><span class="line">Microsoft.TimeSeriesInsights.</span><br><span class="line">Errors were:</span><br><span class="line">Cannot register provider Microsoft.ServiceBus with Azure Resource Manager:</span><br><span class="line">unexpected status 403 with error:</span><br><span class="line">AuthorizationFailed:</span><br><span class="line">The client &#x27;xxx.xxx.xxx@exapmle.co.jp&#x27; with object id &#x27;xxxxx-xxxx-xxxx-xxxx-xxxx&#x27;</span><br><span class="line">does not have authorization to perform action &#x27;Microsoft.ServiceBus/register/action&#x27;</span><br><span class="line">over scope &#x27;/subscriptions/xxxx-xxxx-xxxx-xxxx&#x27; or the scope is invalid.</span><br><span class="line">If access was recently granted, please refresh your credentials..</span><br></pre></td></tr></table></figure></div>

<p>Terraformコード上はVMやStorage Account等、作成権限が確認できているリソースしか記載していませんでしたが、providerなるものを追加する際に権限不足で失敗したとのことです。</p>
<p>ちなみに利用するAzure環境は他チームから払い出された環境であり、Terraform実行時に認証した個人アカウントは権限が絞られています。</p>
<h2 id="リソースプロバイダーとは">リソースプロバイダーとは</h2><p>今回遭遇したエラーのproviderはリソースプロバイダーといい、Azure サービスのリソース プロバイダーとは何かに説明があります。</p>
<p>概要について抜粋します。</p>
<blockquote>
<p>リソース プロバイダーは、Azure サービスの機能を実現する REST 操作のコレクションです。 各リソース プロバイダーには、company-name.service-label という形式の名前空間があります。</p>
</blockquote>
<p>例えばAzureストレージについては、<code>Microsoft.Storage</code>という名前空間で表現されます。</p>
<p>REST APIへリクエストを送信した際にAzureリソースマネージャーがそれを受け取りますが、実際の操作はリソースプロバイダーによって実施します。</p>
<p>下記ページが分かりやすくまとめられています。<br>【図解】初心者向けリソースマネージャーとリソースプロバイダー</p>
<p>リソースプロバイダーはデフォルトで登録されているものもありますが、手動で登録する場合や、Azureポータルにおいてリソースを作ることで自動登録される場合もあります。</p>
<p>今回のエラーは、Terraformプロバイダーの1つであるAzureRM利用時にリソースプロバイダーを登録しようとして発生しました。</p>
<h2 id="Terraform実行時におけるリソースプロバイダー">Terraform実行時におけるリソースプロバイダー</h2><p>AzureにおけるTerraformプロバイダーとしては下記が用意されています。</p>
<ul>
<li>AzureRM</li>
<li>AzureAD</li>
<li>AzureDevops</li>
<li>AzAPI</li>
<li>AzureStack</li>
</ul>
<p>今回はVMやStorage Account等を管理するため、AzureRMを利用していました。</p>
<p>terraform-provider-azurerm&#x2F;internal&#x2F;resourceprovidersのページによると、AzureRMでは複数のリソースプロバイダーを自動で登録するとのことです。</p>
<p>リソースプロバイダーの登録ですが、providerブロックに<code>skip_provider_registration = true</code>とすることで無効化できます。</p>
<p>あるいは環境変数<code>ARM_SKIP_PROVIDER_REGISTRATION</code>を設定することも可能とのことです。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-87pkpw-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-87pkpw-2" title="コードの折り返しを切り替える"></label><figcaption><span>terraform</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">provider</span> <span class="string">&quot;azurerm&quot;</span> {
  skip_provider_registration = true
  features {}
}</pre></td></tr></tbody></table></figure></div>

<p>Azure Provider#skip_provider_registration</p>
<h2 id="解決法">解決法</h2><p>今回発生したエラーの解決には下記1,2の方法が考えられます。</p>
<ol>
<li>Terraform実行ユーザにリソースプロバイダー関連の権限を付与する</li>
<li>事前に手動でリソースプロバイダーを登録し、Terraformのリソースプロバイダー登録を無効化する</li>
</ol>
<p>1つ目の方法ですが、サブスクリプション全体に対するリソースごとの<code>company-name.service-label/register/action</code>等の権限を付与する形です。権限を付与し<code>terraform plan</code>を実行したタイミングでリソースプロバイダーが登録されます。</p>
<p>2つ目の方法は、手動でリソースプロバイダーを登録しておき、Terraform実行時には<code>skip_provider_registration</code>の記載や環境変数でリソースプロバイダー登録をスキップする方法です。手動でリソースプロバイダーを登録する場合は、Azureポータルのサブスクリプションからリソースプロバイダーを選択し、該当リソースプロバイダーを登録します。</p>
<p>私たちは、リソースプロバイダーの登録をより上位の管理者に依頼して実施するフローを作成した上で、2つ目の方法を採用しました。同一サブスクリプション内で複数チームがそれぞれTerraform実行するようなケースにおいて、利用可能なリソースを制限する場合等に採り得る方法なのかと思います。</p>
<h2 id="さいごに">さいごに</h2><p>これまで強い権限でTerraformを操作していたためリソースプロバイダーを意識することは無かったのですが、今回はチームの運用ポリシーとして権限が制限されていたため改めて調査しました。</p>
<p>リソースプロバイダーの登録を必要最低限にすることで、不要なリソースをサブスクリプション内で作成されないようにできます。リソースプロバイダー登録の権限を分離することで、より厳格にAzure環境を運用できます。</p>
<p>より良い運用方法について、今後も考えていきたいと思います。</p>
<p>また、今回の記事で扱ったリソースプロバイダーについての調査は、同チームの戸井田さんにご協力いただきました。ありがとうございました！</p>
]]></content>
    <summary type="html">昨今のOpenAI需要によって、Azure環境の利用を本格的に始める方も多いかと思います。今回はAzure環境でTerraformを利用する際、裏で動いているリソースプロバイダーについてご紹介します。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Azure" scheme="https://future-architect.github.io/tags/Azure/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
  </entry>
  <entry>
    <title>Terraform連載2024 テストとモックを使ってみる</title>
    <link href="https://future-architect.github.io/articles/20240321a/"/>
    <id>https://future-architect.github.io/articles/20240321a/</id>
    <published>2024-03-20T15:00:00.000Z</published>
    <updated>2024-03-20T15:00:00.000Z</updated>
    <author><name>前原応光</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2024/20240321a/top.png" alt="" width="800" height="555">

<p>Terraform連載2024を の7本目です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは！ TIG Chill Architect の前原です。<br>Terraform を使ってクラウド環境とか構築する人は多くいると思います。<br>その中で毎回のようにどうやってテストをやるべきなのかなーとか悩んで、結果大したことも出来ずにリリースまで来てしまったということはないでしょうか。</p>
<p>今回は、その悩みを少しでも払拭できないかと思い、Terraform v1.6 とv1.7 で提供されたtestsとmocksに触れていきたいと思います。</p>
<h2 id="今までの試み">今までの試み</h2><p>今までは、TFLintを導入し、コードチェックを行うことを実施していました。</p>
<p>ただし、この方式だと静的コード解析のため、より踏み込んだところまでいけませんでした。静的コード解析にプラスして動的なテストができると良いなーって感じていました（terratestというのもありますが、導入コストが高い）</p>
<h2 id="terraform-test">terraform test</h2><p><code>terraform test</code>は、実際にコードを実行し、動的にテストを行うことができます。</p>
<p>これによりコードの信頼性、品質を高めることができます。</p>
<p>module に対してのテストが強力ですが、module 以外でも利用可能です。</p>
<h3 id="こんなことができる">こんなことができる</h3><p><code>terraform test</code> は、以下のようなことができます（他にもできることがある）</p>
<ul>
<li><code>terraform plan</code> を実行し、期待する結果が得られるかを確認できる</li>
<li><code>terraorm apply</code> を実行した時に期待する結果であるか確認できる<ul>
<li>実際にリソースを作成し、削除まで行う</li>
</ul>
</li>
<li>期待する結果を<code>condition</code>に書いて判定する</li>
<li>Data Source を取得できる</li>
</ul>
<h3 id="実際に動かしてみる">実際に動かしてみる</h3><p>それでは、実際に簡単なコードを作成して動かしてみたいと思います。<br>Terraform のバージョンは、1.6以上にしてください。<br>バージョンの切り替えが面倒だなって思ったらtfenvをインストールすると少し幸せになれます。<br>また、クラウドは、AWS です。</p>
<p>S3 バケットを作成し、そのS3 バケット名が期待する名前なのかをテストするコードを書きたいと思います。<br>正直、このコードがテスト用途として必要か？ というのはありますが、まずは慣れるというのを目的にしたいと思います。</p>
<p>ディレクトリは、以下の構成です。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">.</span><br><span class="line">├── backend.tf</span><br><span class="line">├── local.tf</span><br><span class="line">├── s3_bucket.tf</span><br><span class="line">├── s3_bucket.tftest.hcl</span><br><span class="line">└── versions.tf</span><br></pre></td></tr></table></figure>

<p>コードは、以下です。<br><code>backend.tf</code> を省略していますが、よしなにお願いします。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-kr7qnb-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-1" title="コードの折り返しを切り替える"></label><figcaption><span>local.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">locals</span> {
  project_name  = <span class="string">&quot;sample&quot;</span>
  region        = <span class="string">&quot;ap-northeast-1&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-kr7qnb-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-2" title="コードの折り返しを切り替える"></label><figcaption><span>s3_bucket.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;test001&quot;</span> {
  bucket = <span class="string">&quot;<span class="variable">${terraform.workspace}</span>-<span class="variable">${local.project_name}</span>-test001&quot;</span>
  tags = {
    Name        = <span class="string">&quot;<span class="variable">${terraform.workspace}</span>-<span class="variable">${local.project_name}</span>-test001&quot;</span>
    Environment = <span class="keyword">terraform</span>.workspace
  }
}</pre></td></tr></tbody></table></figure></div>

<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-kr7qnb-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-3" title="コードの折り返しを切り替える"></label><figcaption><span>versions.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">terraform</span> {
  required_version = <span class="string">&quot;~&gt; 1.7.5&quot;</span>
  required_providers {
    aws = {
      source  = <span class="string">&quot;hashicorp/aws&quot;</span>
      version = <span class="string">&quot;5.41.0&quot;</span>
    }
  }
}
<span class="line"></span>
<span class="keyword">provider</span> <span class="string">&quot;aws&quot;</span> {
  region = local.region
}</pre></td></tr></tbody></table></figure></div>

<p>テストコードを作成します。<br>conditionに記述したS3 バケット名と実際のS3 バケット名を比較して問題ないかをチェックしています。<br>仮に期待していない値の場合は、<code>error_message</code>の値が出力されます。</p>
<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-kr7qnb-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-4" title="コードの折り返しを切り替える"></label><figcaption><span>s3_bucket.tftest.hcl</span></figcaption><table><tbody><tr><td class="code"><pre>run <span class="string">&quot;test&quot;</span> {
  assert {
    condition     = aws_s3_bucket.test.bucket == <span class="string">&quot;<span class="variable">${terraform.workspace}</span>-<span class="variable">${local.project_name}</span>-test&quot;</span>
    error_message = <span class="string">&quot;S3 bucket name did not match expected&quot;</span>
  }
}</pre></td></tr></tbody></table></figure></div>

<p>テストが成功した時は、以下の出力となります。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform <span class="built_in">test</span></span></span><br><span class="line"></span><br><span class="line">tests/s3_bucket.tftest.hcl... in progress</span><br><span class="line">  run &quot;test&quot;... pass</span><br><span class="line">tests/s3_bucket.tftest.hcl... tearing down</span><br><span class="line">tests/s3_bucket.tftest.hcl... pass</span><br><span class="line"></span><br><span class="line">Success! 1 passed, 0 failed.</span><br></pre></td></tr></table></figure>

<p>失敗した時は、以下です。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-kr7qnb-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform <span class="built_in">test</span></span></span><br><span class="line"></span><br><span class="line">tests/s3_bucket.tftest.hcl... in progress</span><br><span class="line">  run &quot;test&quot;... fail</span><br><span class="line">╷</span><br><span class="line">│ Error: Test assertion failed</span><br><span class="line">│</span><br><span class="line">│   on tests/s3_bucket.tftest.hcl line 4, in run &quot;test&quot;:</span><br><span class="line">│    4:     condition     = aws_s3_bucket.test.bucket == &quot;$&#123;terraform.workspace&#125;-$&#123;local.project_name&#125;-test1&quot;</span><br><span class="line">│     ├────────────────</span><br><span class="line">│     │ aws_s3_bucket.test.bucket is &quot;dev-sample-test&quot;</span><br><span class="line">│     │ local.project_name is &quot;sample&quot;</span><br><span class="line">│     │ terraform.workspace is &quot;dev&quot;</span><br><span class="line">│</span><br><span class="line">│ S3 bucket name did not match expected</span><br><span class="line">╵</span><br><span class="line">tests/s3_bucket.tftest.hcl... tearing down</span><br><span class="line">tests/s3_bucket.tftest.hcl... fail</span><br><span class="line"></span><br><span class="line">Failure! 0 passed, 1 failed.</span><br></pre></td></tr></table></figure></div>

<p>テストファイルをカレントディレクトリに配置していますが、ディレクトリを分けることも可能です。<br>例えば以下のようにできます。<br><code>tests</code>ディレクトリであればデフォルト指定されているため、読み込まれます。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">.</span><br><span class="line">├── backend.tf</span><br><span class="line">├── local.tf</span><br><span class="line">├── s3_bucket.tf</span><br><span class="line">├── tests</span><br><span class="line">│   └── s3_bucket.tftest.hcl</span><br><span class="line">└── versions.tf</span><br></pre></td></tr></table></figure>

<p>別のディレクトリ名で実行したい場合は、以下のように指定できます。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">terraform <span class="built_in">test</span> -test-directory=hoge</span><br></pre></td></tr></table></figure>

<p>今回は、<code>apply</code>を実行したのですが、<code>plan</code>を実行したいときは、以下のように<code>command</code>　指定します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-kr7qnb-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-6" title="コードの折り返しを切り替える"></label><figcaption><span>s3_bucket.tftest.hcl</span></figcaption><table><tbody><tr><td class="code"><pre>run <span class="string">&quot;test&quot;</span> {
  command = plan
  assert {
    condition     = aws_s3_bucket.test.bucket == <span class="string">&quot;<span class="variable">${terraform.workspace}</span>-<span class="variable">${local.project_name}</span>-test&quot;</span>
    error_message = <span class="string">&quot;S3 bucket name did not match expected&quot;</span>
  }
}</pre></td></tr></tbody></table></figure></div>

<p>注意としては、<code>apply</code>を実行する際に既にリソースが作成されている場合は、以下のエラーが出力されます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-kr7qnb-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform <span class="built_in">test</span></span></span><br><span class="line">sample.tftest.hcl... in progress</span><br><span class="line">  run &quot;sample&quot;... fail</span><br><span class="line">╷</span><br><span class="line">│ Error: creating S3 Bucket (dev-sample-test): operation error S3: CreateBucket, https response error StatusCode: 409, RequestID: 5FC2KQ07G6SGK0W1, HostID: uhahHveFHZBJiEnSDfTsnqVd6gQBmRx+GabbLzMB2jlBkxlPGjblnyuY17MHMoo3UC2uODLSMWk=, BucketAlreadyOwnedByYou:</span><br></pre></td></tr></table></figure></div>

<h2 id="terraform-mock">terraform mock</h2><p><code>terraform test</code> は、非常に便利で日々の運用を良くするための強力なツールであることがわかるかと思います。<br>ただ、ローカル開発する中で強力なキーを持たせたくないや、使用できない状況があると思います。<br>また、CI を回すためにも必要といったケースもあるかと思います。<br>そういった時に便利な<code>terraform mock</code>を使っていきたいと思います。</p>
<h3 id="実際に動かしてみる-1">実際に動かしてみる</h3><p>ディレクトリは、以下の構成です。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">.</span><br><span class="line">├── backend.tf</span><br><span class="line">├── local.tf</span><br><span class="line">├── mocked_providers.tftest.hcl</span><br><span class="line">├── s3_bucket.tf</span><br><span class="line">├── s3_bucket.tftest.hcl</span><br><span class="line">└── versions.tf</span><br></pre></td></tr></table></figure>

<p><code>terraform test</code> をベースに作成しているため差分のみを記載します。<br>以下にモック用のプロバイダを指定します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-kr7qnb-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-8" title="コードの折り返しを切り替える"></label><figcaption><span>mocked_providers.tftest.hcl</span></figcaption><table><tbody><tr><td class="code"><pre>mock_provider <span class="string">&quot;aws&quot;</span> {
  alias = <span class="string">&quot;fake&quot;</span>
}
<span class="line"></span>
run <span class="string">&quot;use_mocked_provider&quot;</span> {
  providers = {
    aws = aws.fake
  }
}</pre></td></tr></tbody></table></figure></div>

<p><code>versions.tf</code>のprovider 定義を削除します。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-kr7qnb-9" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-9" title="コードの折り返しを切り替える"></label><figcaption><span>versions.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">terraform</span> {
  required_version = <span class="string">&quot;~&gt; 1.7.5&quot;</span>
  required_providers {
    aws = {
      source  = <span class="string">&quot;hashicorp/aws&quot;</span>
      version = <span class="string">&quot;5.41.0&quot;</span>
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<p>モックを動かす際も<code>terraform test</code> コマンドを使用します。<br>実際に実行したいと思います。<br>アクセスキーを利用せずとも実行できることが確認できるかと思います。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform <span class="built_in">test</span></span></span><br><span class="line"></span><br><span class="line">mocked_providers.tftest.hcl... in progress</span><br><span class="line">  run &quot;use_mocked_provider&quot;... pass</span><br><span class="line">mocked_providers.tftest.hcl... tearing down</span><br><span class="line">mocked_providers.tftest.hcl... pass</span><br><span class="line">s3_bucket.tftest.hcl... in progress</span><br><span class="line">  run &quot;test&quot;... pass</span><br><span class="line">s3_bucket.tftest.hcl... tearing down</span><br><span class="line">s3_bucket.tftest.hcl... pass</span><br><span class="line"></span><br><span class="line">Success! 2 passed, 0 failed.</span><br></pre></td></tr></table></figure>

<p>もしモックと<code>terraform test</code>のように実際のリソースに対してテストを行いたい場合は、以下のプロバイダ指定で行うことができます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-kr7qnb-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-kr7qnb-10" title="コードの折り返しを切り替える"></label><figcaption><span>mocked_providers.tftest.hcl</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">provider</span> <span class="string">&quot;aws&quot;</span> {}
<span class="line"></span>
mock_provider <span class="string">&quot;aws&quot;</span> {
  alias = <span class="string">&quot;fake&quot;</span>
}
<span class="line"></span>
run <span class="string">&quot;use_real_provider&quot;</span> {
  providers = {
    aws = aws
  }
}
<span class="line"></span>
run <span class="string">&quot;use_mocked_provider&quot;</span> {
  providers = {
    aws = aws.fake
  }
}</pre></td></tr></tbody></table></figure></div>

<p>他にもモックで返される値をよしなに変更するといったことも可能です。</p>
<h3 id="どういったケースで使うのがいいのだろうか">どういったケースで使うのがいいのだろうか</h3><p>すべてのリソースに対して無邪気にテストコードを書くのは、とても非効率です。<br>そのため、テストを行う場合の考慮すべきポイントを以下にまとめたいと思います。<br>（個人的なポイントのため、あくまで参考レベルです）</p>
<h4 id="クリティカルなインフラ系のリソース">クリティカルなインフラ系のリソース</h4><p>セキュリティグループ、IAMポリシー、VPC設定など、セキュリティやアプリケーションの正常な動作に直接影響を与えるクリティカルなインフラ系のリソースに対してテストを行うのが良いと思います。</p>
<h4 id="依存関係のあるリソース">依存関係のあるリソース</h4><p>複数のリソース間で複雑な依存関係を持つパターン（例えば、セキュリティグループのルールが特定のリソースに依存している場合など）。<br>これらの依存関係を正確に反映しているかを確認するときに良いと思います。</p>
<h4 id="コストに大きな影響を与えるリソース">コストに大きな影響を与えるリソース</h4><p>インスタンスタイプによっては、大きくコストに跳ねるため、サイズ、数などをテストすることは有効です。<br>これにより、予期しないコストの発生を防ぐことができます。</p>
<h4 id="変更頻度の高いリソース">変更頻度の高いリソース</h4><p>変更頻度が高いとミスが発生する可能性が高くなります。<br>そのため、それらに対してテストを行うのは有効かと思います。</p>
<h4 id="モジュール">モジュール</h4><p>モジュールは、複数のリソースを包括的に管理するため、複雑な構成となります。<br>モジュールの期待する結果をテストでカバーできるため、再利用性、品質の向上につながるかと思います。</p>
<h2 id="まとめ">まとめ</h2><p>いかがでしたでしょうか。<br>今回紹介したTerraform の機能を利用することで少しでもテストの有効性や導入のきっかけになれば幸いです。</p>
<h2 id="参考">参考</h2><ul>
<li>Terraform 1.7 adds test mocking and config-driven remove</li>
<li>Write Terraform Tests</li>
<li>Mocks</li>
<li>Tests</li>
</ul>
]]></content>
    <summary type="html">Terraform を使ってクラウド環境とか構築する人は多くいると思います。その中で毎回のようにどうやってテストをやるべきなのかなーとか悩んで、結果大したことも出来ずにリリースまで来てしまったということはないでしょうか。今回は、その悩みを少しでも払拭できないかと思い、Terraform v1.6 とv1.7 で提供されたtestsに触れていきたいと思います。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="テスト" scheme="https://future-architect.github.io/tags/%E3%83%86%E3%82%B9%E3%83%88/"/>
    <category term="モック" scheme="https://future-architect.github.io/tags/%E3%83%A2%E3%83%83%E3%82%AF/"/>
  </entry>
  <entry>
    <title>cfn-guardを使ってTerraformをポリシーチェックしようとした話</title>
    <link href="https://future-architect.github.io/articles/20240318a/"/>
    <id>https://future-architect.github.io/articles/20240318a/</id>
    <published>2024-03-17T15:00:00.000Z</published>
    <updated>2024-03-17T15:00:00.000Z</updated>
    <author><name>原木翔</name></author>
    <content type="html"><![CDATA[<p>Terraform連載2024を の6本目です。</p>
<h2 id="導入">導入</h2><p>インフラエンジニアとして働いているTIGの原木です。</p>
<p>cfn-guardを使用してTerraformをポリシーチェックしようとした話をします。</p>
<h2 id="cfn-guardとは？">cfn-guardとは？</h2><p>cfn-guardのcfnとはAWSのCloudFormation(AWSのIaCソリューションのこと)の略称です。</p>
<p>このツールはCloudFormationを使ってAWSのリソースをデプロイするときにその内容をチェックするポリシーチェックツールとしてよく使われています。</p>
<p>しかし、cfn-guardはその名前に反して、CloudFormationに限らず、JSON&#x2F;YAMLファイルに対する汎用的なポリシーチェックツールとしても使用できます。</p>
<p>READMEの記載にも、次の通り説明があります。</p>
<blockquote>
<p>Guard offers a policy-as-code domain-specific language (DSL) to write rules and validate JSON- and YAML-formatted data such as CloudFormation Templates, K8s configurations, and Terraform JSON plans&#x2F;configurations against those rules.</p>
<p>Guardは、CloudFormationテンプレート、K8sコンフィグレーション、TerraformのJSONプラン&#x2F;コンフィグレーションなどのJSONやYAMLフォーマットのデータに対して、ルールを記述し検証するためのPolicy as Codeなドメイン固有言語(DSL)を提供します。</p>
</blockquote>
<p>AWS Certified Securityの勉強をしていて本ツールの名前を知り、READMEを見て、自分は興味を持ちました。</p>
<h2 id="cfn-guardでTerraformをチェックしようとしたモチベーション">cfn-guardでTerraformをチェックしようとしたモチベーション</h2><p>Terraformのポリシーチェックとしては、過去にFuture技術ブログで紹介したtflintやterraform validator<sup id="fnref:1">1</sup>、tfsec<sup id="fnref:2">2</sup>等すでに様々なツールがあります。</p>
<p>その中でなぜあえて、cfn-guardをTerraform planをチェックしようとしたのか？</p>
<p>それはcfn-guardに読み込ませるルール表となるCFn Guard DSLとAWSマネージドサービスの力を借りてインフラをデプロイする前から後まで一貫したポリシーチェックができるのではないか。と考えたためです。</p>
<p>一度CFn Guard DSL(ポリシールール)を書くことで二度おいしいメリットがあると考えました。</p>
<ul>
<li>Terraformのコーディング中に、cfn-guardによりユニットテストを動かす感覚でポリシーチェックを随時できるようになります</li>
<li>Terraformを使ってAWSインフラを構築後、意図しない形でリソースが変更されてもAWS ConfigによりトリガーされたCFn Guardルールのスキャンによってインフラのドリフトを検知できるようになります</li>
</ul>
<p>CloudFormation Guard で Policy as Code！ 実際どうよ？ &#x2F; Policy as Code with CloudFormation Guardのスライドをお借りすると次のようなイメージです。</p>
<img fetchpriority="high" src="/images/2024/20240318a/image.png" alt="image.png" width="1200" height="682">

<p>このような <strong>青写真</strong> を描きました。</p>
<p>このブログでは、cfn-guardを検証し…そして、思ってたのと違った!!という話をしたいと思います。</p>
<h2 id="なにはともあれ実践してみよう">なにはともあれ実践してみよう</h2><p>一番基礎的な使い方として、S3ファイルをデプロイするterraformのファイルをチェックする方法について書きたいと思います。</p>
<p>前提として、cfn-guardはHCLファイル(要は.tfファイル)を直接チェックできず、JSON&#x2F;YAMLファイルを読み込ませないといけないので、 <code>terraform plan</code> の実行結果からJSONファイルを作成する必要があります。</p>
<p>下記のようにS3バケットを作成するHCLファイルがあったとしましょう。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-16y3xns-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-16y3xns-1" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># provider等は省略します</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;my_bucket&quot;</span> {
  bucket = <span class="string">&quot;my-bucket&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_s3_bucket_public_access_block&quot;</span> <span class="string">&quot;my_bucket&quot;</span> {
  bucket = aws_s3_bucket.my_bucket.id
<span class="line"></span>
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}</pre></td></tr></tbody></table></figure></div>

<p>このファイルがまだ未作成の場合、次のようにコマンドを実行することで<br>作成後に想定されるリソース構成をJSONファイルで出力できます。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">terraform plan -out tfplan.bin</span><br><span class="line">terraform show --json tfplan.bin &gt; tfplan.json</span><br></pre></td></tr></table></figure>

<p><code>tfplan.json</code> のファイル構造を分解して中身を見てみましょう。<br>※そのままだと見づらいのでJSONファイルをサブセットであるYAMLファイルに変換して表示します。<br>※YAMLファイルでも素のJSONファイルでもcfn-guardは動かすことができます。</p>
<div class="code-block"><figure class="highlight yaml"><input type="checkbox" id="code-wrap-16y3xns-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-16y3xns-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="attr">format_version:</span> <span class="string">&quot;1.2&quot;</span></span><br><span class="line"><span class="attr">terraform_version:</span> <span class="number">1.6</span><span class="number">.1</span></span><br><span class="line"><span class="attr">planned_values:</span></span><br><span class="line">  <span class="string">//</span> <span class="string">terraformがplanしたリソースの最終的な構成情報</span></span><br><span class="line">  <span class="attr">root_module:</span></span><br><span class="line">    <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">address:</span> <span class="string">aws_s3_bucket.my_bucket</span></span><br><span class="line">        <span class="attr">mode:</span> <span class="string">managed</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">aws_s3_bucket</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">my_bucket</span></span><br><span class="line">        <span class="attr">provider_name:</span> <span class="string">registry.terraform.io/hashicorp/aws</span></span><br><span class="line">        <span class="attr">schema_version:</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">values:</span></span><br><span class="line">          <span class="attr">bucket:</span> <span class="string">my-bucket1</span></span><br><span class="line">          <span class="attr">force_destroy:</span> <span class="literal">false</span></span><br><span class="line">          <span class="attr">tags:</span> <span class="literal">null</span></span><br><span class="line">          <span class="attr">tags_all:</span></span><br><span class="line">            <span class="attr">env:</span> <span class="string">dev</span></span><br><span class="line">          <span class="attr">timeouts:</span> <span class="literal">null</span></span><br><span class="line">        <span class="attr">sensitive_values:</span></span><br><span class="line">          <span class="attr">cors_rule:</span> []</span><br><span class="line">          <span class="attr">grant:</span> []</span><br><span class="line">          <span class="attr">lifecycle_rule:</span> []</span><br><span class="line">          <span class="attr">logging:</span> []</span><br><span class="line">          <span class="attr">object_lock_configuration:</span> []</span><br><span class="line">          <span class="attr">replication_configuration:</span> []</span><br><span class="line">          <span class="attr">server_side_encryption_configuration:</span> []</span><br><span class="line">          <span class="attr">tags_all:</span> &#123;&#125;</span><br><span class="line">          <span class="attr">versioning:</span> []</span><br><span class="line">          <span class="attr">website:</span> []</span><br><span class="line">      <span class="bullet">-</span> <span class="attr">address:</span> <span class="string">aws_s3_bucket_public_access_block.my_bucket</span></span><br><span class="line">        <span class="attr">mode:</span> <span class="string">managed</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">aws_s3_bucket_public_access_block</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">my_bucket</span></span><br><span class="line">        <span class="attr">provider_name:</span> <span class="string">registry.terraform.io/hashicorp/aws</span></span><br><span class="line">        <span class="attr">schema_version:</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">values:</span></span><br><span class="line">          <span class="attr">block_public_acls:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">block_public_policy:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">ignore_public_acls:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">restrict_public_buckets:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">sensitive_values:</span> &#123;&#125;</span><br><span class="line"><span class="attr">resource_changes:</span></span><br><span class="line">  <span class="string">//</span> <span class="string">既存リソースに対する変更内容</span></span><br><span class="line">  <span class="string">//</span> <span class="string">省略</span></span><br><span class="line"><span class="attr">configuration:</span></span><br><span class="line">  <span class="string">//</span> <span class="string">元の状態に適用される構成のこと</span></span><br><span class="line">  <span class="string">//</span> <span class="string">省略</span></span><br><span class="line"><span class="attr">relevant_attributes:</span></span><br><span class="line">  <span class="string">//</span> <span class="string">変更されたリソースの関連属性</span></span><br><span class="line">  <span class="string">//</span> <span class="string">省略</span></span><br><span class="line"><span class="attr">timestamp:</span> <span class="string">&quot;2024-03-17T05:58:17Z&quot;</span></span><br><span class="line"><span class="attr">errored:</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure></div>

<p>上記リソースをチェックするためのリソースポリシーを書いてみます。</p>
<p>よくあるリソースポリシーとして</p>
<ul>
<li>S3のバケット名が特定の命名規則にしたがっていること</li>
<li>リソースに特定の環境を示すタグが入っていること</li>
</ul>
<p>をチェックしたいと思います。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-16y3xns-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-16y3xns-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">let aws_s3_bucket_resources = planned_values.root_module.resources[type == &quot;aws_s3_bucket&quot;]</span><br><span class="line"></span><br><span class="line">rule aws_s3_bucket_rule when %aws_s3_bucket_resources !empty &#123;</span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">バケット名は <span class="string">&quot;test-&quot;</span> で始まる必要があります</span></span><br><span class="line"><span class="meta prompt_">  %</span><span class="language-bash">aws_s3_bucket_resources.values.bucket == /^<span class="built_in">test</span>-.*/</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash"><span class="string">&quot;env&quot;</span> タグが必ず含まれること</span></span><br><span class="line">  let required_tags = %aws_s3_bucket_resources.values.tags_all[</span><br><span class="line">      Key == &#x27;env&#x27; ]</span><br><span class="line"><span class="meta prompt_">  %</span><span class="language-bash">required_tags[*] &#123;</span></span><br><span class="line">      Value IN [&#x27;dev&#x27;, &#x27;stg&#x27;, &#x27;prod&#x27;, &#x27;demo&#x27;]</span><br><span class="line">      &lt;&lt;Tag must have a permitted value&gt;&gt;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>このルールに従っているか実際に <code>cfn-guard</code> を動かし、チェックしてみましょう。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-16y3xns-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-16y3xns-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cfn-guard validate -r s3_template_example.guard -d infrastructure/tfplan1.json -o yaml</span></span><br><span class="line">tfplan1.json Status = FAIL</span><br><span class="line">FAILED rules</span><br><span class="line">s3_template_example.guard/aws_s3_bucket_rule                    FAIL</span><br><span class="line">---</span><br><span class="line">name: tfplan1.json</span><br><span class="line">metadata: &#123;&#125;</span><br><span class="line">status: FAIL</span><br><span class="line">not_compliant:</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">出力内容はわかりやすくするために途中端折ってます</span></span><br><span class="line">- Rule:</span><br><span class="line">    name: aws_s3_bucket_rule</span><br><span class="line">    checks:</span><br><span class="line">    - Clause:</span><br><span class="line">        Binary:</span><br><span class="line">          context: &#x27; %aws_s3_bucket_resources[*].values.bucket EQUALS  &quot;/^test-.*/&quot;&#x27;</span><br><span class="line">          messages:</span><br><span class="line">            custom_message: &#x27;&#x27;</span><br><span class="line">            error_message: Check was not compliant as property value [Path=/planned_values/root_module/resources/0/values/bucket[L:0,C:286] Value=&quot;my-bucket&quot;] not equal to value [Path=[L:0,C:0] Value=&quot;/^test-.*/&quot;].</span><br><span class="line">          check:</span><br><span class="line">            Resolved:</span><br><span class="line">              from:</span><br><span class="line">                path: /planned_values/root_module/resources/0/values/bucket</span><br><span class="line">                value: my-bucket</span><br><span class="line">              to:</span><br><span class="line">                path: &#x27;&#x27;</span><br><span class="line">                value: /^test-.*/</span><br><span class="line">              comparison:</span><br><span class="line">              - Eq</span><br><span class="line">              - false</span><br></pre></td></tr></table></figure></div>

<p>エラーになりました。contextを確認すると、 <code>context: &#39; %aws_s3_bucket_resources[*].values.bucket EQUALS  &quot;/^test-.*/&quot;&#39;</code> とあるようにバケットの命名規則がルールに従っていないことがわかります。</p>
<p>そこでバケット名を修正し、再度 <code>cfn-guard</code> にかけてみます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-16y3xns-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-16y3xns-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">修正したs3.tf及びyamlファイルは割愛</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cfn-guard validate -r s3_template_example.guard -d infrastructure/tfplan2.json -o yaml</span></span><br><span class="line">name: tfplan2.json</span><br><span class="line">metadata: &#123;&#125;</span><br><span class="line">status: PASS</span><br><span class="line">not_compliant: []</span><br><span class="line">not_applicable: []</span><br><span class="line">compliant:</span><br><span class="line">- aws_s3_bucket_rule</span><br><span class="line">- public_access_block_resources_rule</span><br></pre></td></tr></table></figure></div>

<p>今度は通りました。</p>
<p>序の口ではありますが、cfn-guardを使ったチェック方法について、雰囲気は掴めたのではないかと思います。しかし、ここから先、cfn-guardを深掘りするうちにギャップが広がっていくことに気づきました。</p>
<h2 id="ここが思ってたのと違ってたよという話">ここが思ってたのと違ってたよという話</h2><p>当初の自分の妄想では、cfn-guardとは、AWSのリソースAPIにアクセスしていい感じにチェックするツールなのかなとふわっと思ってました。ですが、実践例にあるように実態はどうでしょうか？</p>
<p>カンの良い方はすぐに気づかれたかもしれません。</p>
<p>CloudFormationを例に先ほどと同じことをしてみましょう。</p>
<div class="code-block"><figure class="highlight yaml"><input type="checkbox" id="code-wrap-16y3xns-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-16y3xns-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="number">2010-09-09</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">MyS3Bucket4646DF6F:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::S3::Bucket</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">BucketName:</span> <span class="string">my-bucket</span></span><br><span class="line">    <span class="attr">UpdateReplacePolicy:</span> <span class="string">Delete</span></span><br><span class="line">    <span class="attr">DeletionPolicy:</span> <span class="string">Delete</span></span><br><span class="line">    <span class="attr">Metadata:</span></span><br><span class="line">      <span class="attr">aws:cdk:path:</span> <span class="string">CdkAppStack/MyS3Bucket/Resource</span></span><br></pre></td></tr></table></figure></div>

<p>というファイルに対してS3のバケット名の命名規則をチェックします。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-16y3xns-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-16y3xns-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">let buckets = Resources.*[ Type == &#x27;AWS::S3::Bucket&#x27; ]</span><br><span class="line"></span><br><span class="line">rule BucketEncryption when %buckets !empty &#123;</span><br><span class="line"><span class="meta prompt_">  %</span><span class="language-bash">buckets.Properties &#123;</span></span><br><span class="line">    BucketName == /^test-.*/</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>別物やんけ。</p>
<p>その通りです。なぜなら、CloudFormationとTerraform planではファイルの構造が全然異なりますので。</p>
<p>cfn-guardの実態は、CFn Guard DSLに基づきJSONやYAMLなどの構造型データを検査する、ある意味シンプルな構文解析ツールです。</p>
<p>したがって、ポリシーファイルについてCloudFormation向けはCloudFormation向け、Terraform plan向けはTerraform plan向けに書く必要があります。そして後者のTerraform plan向けのポリシーファイルは、当然AWS Config上で動きません。</p>
<p>ここに当初の構想はからくも崩れたのでした。</p>
<p>ECSのTask Definitionのようにインラインで文字列化したJSONファイルをいい感じにパースする方法が見つからなかった、tagチェックでtagsとtags_allを別々にチェックする必要があった、そもそも構文エラー時説明してるようで何も説明してくれないエラーログ等、細かいことを言い出すときりがない不満があり、最終的に自分はおとなしくtflintに戻りました。</p>
<h2 id="さいごに">さいごに</h2><p>Terraformユーザーには、cfn-guardの扱いは少々難しいところがあるという話でした。</p>
<p>ポリシールール等の設定について最近はChatGPT先生に下書きをお願いすることが多いのですが、彼女に自由に書かせたら、明らかにAWS CloudFormationテンプレート向けのguardファイルをTerraformと言い張ったのは悲しかったです。</p>
<p>WHY?と聞いたら次の通り開き直った回答が返ってきました。<br><img src="/images/2024/20240318a/image_2.png" alt="" width="1200" height="1142" loading="lazy"></p>
<p>しかし、AWS CDKを使ってCloufFormationのテンプレートファイルを生成し、AWSリソースのデプロイを行っているユーザーにとって強力なポリシーチェックツールなのは間違いありません。</p>
<p>CFn Guard Rules Registryには、ルールの実装例が多数掲載されております。<br>Amazon Web Services’ Well-Architected Framework Reliability Pillar等、インフラエンジニアが非機能要件を考える時のベストプラクティスを実装したポリシーファイル等もあり、痒い所に手が届く例となっています。</p>
<p>以上、参考になれば幸いです。</p>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">現在は <code>gcloud beta terraform vet</code> として提供中</span> ↩</li><li id="fn:2"><span style="vertical-align: top; padding-right: 10px;">2.</span><span style="vertical-align: top;">現在は <code>trivyの1機能</code> として提供中</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">cfn-guardを使用してTerraformをポリシーチェックしようとした話をします。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Policy-as-Code" scheme="https://future-architect.github.io/tags/Policy-as-Code/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
  </entry>
  <entry>
    <title>サービスの多国展開を支えるTerraform構成</title>
    <link href="https://future-architect.github.io/articles/20240315a/"/>
    <id>https://future-architect.github.io/articles/20240315a/</id>
    <published>2024-03-14T15:00:00.000Z</published>
    <updated>2024-03-14T15:00:00.000Z</updated>
    <author><name>岸下優介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2024/20240315a/image.png" alt="" width="1088" height="542">

<p>Terraform連載2024の5日目です。</p>
<h2 id="はじめに">はじめに</h2><p>IaCを利用してインフラを構成することで、構築忘れや設定ミスといったイージーなミスが減らせるようになりました。とはいえ、展開していく範囲が増えれば増えるほどコードの量も増えていくので、このリソースはどこで作ったっけ…みたいなことが起きてしまいます。</p>
<p>現在の業務ではサービスの海外展開に携わっており、まさに多国展開絶賛実施中という状態です。その際、スペックは同じでもリージョンのみが異なるリソースを作成することが多々あり、環境の管理方法って大切だなーと実感しております。</p>
<p>そこで本記事では、Terraformを利用してシステムを他リージョンへロールアウトする場合のリソース管理・展開方法を3つ挙げてみました。もちろん、他にもたくさんあると思いますので、本記事が参考になると幸いです。</p>
<p>また、本連載の1日目でも伊藤さんがマルチリージョンによるDR（Disaster Recovery）戦略についての記事を書かれているので、こちらも参考にしてください。</p>
<p>参考：TerraformにおけるDR戦略を考える</p>
<h2 id="仮定">仮定</h2><p>以下のような前提で考えてみます。</p>
<ul>
<li>Google Cloudを利用<ul>
<li>他クラウドベンダーでも応用可能だと思います</li>
</ul>
</li>
<li>各環境（Development&#x2F;Staging&#x2F;Production）毎でプロジェクトは同じになる<ul>
<li>dev-app&#x2F;stg-app&#x2F;prd-appの様に3つのプロジェクトが存在します</li>
</ul>
</li>
<li>ほぼ同じシステムを他リージョンへロールアウトしていく<ul>
<li>インスタンスのマシンスペックなど、リソース周りはカスタム可能にしたいと思います</li>
</ul>
</li>
<li>Terraformを実行するためのBastionサーバーが存在する<ul>
<li>各環境のBastionサーバーからTerraformをApplyすることになります</li>
<li>各BastionサーバーでBackendバケットへのアクセスは既に認証済みとなっています</li>
</ul>
</li>
</ul>
<h2 id="構成案（1）：ディレクトリ分けのみで管理する">構成案（1）：ディレクトリ分けのみで管理する</h2><p>以下のように環境、リージョン<sup id="fnref:1">1</sup>をそれぞれディレクトリ分けして管理します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">envs</span><br><span class="line">├── modules</span><br><span class="line">│   └── ...</span><br><span class="line">├── development</span><br><span class="line">│   ├── common</span><br><span class="line">│   │   ├── backend.tf</span><br><span class="line">│   │   ├── compute_network.tf</span><br><span class="line">│   │   ├── project.tf</span><br><span class="line">│   │   ├── variable.tf</span><br><span class="line">│   │   └── versions.tf</span><br><span class="line">│   ├── sydney</span><br><span class="line">│   │   ├── backend.tf</span><br><span class="line">│   │   ├── compute_subnetwork.tf</span><br><span class="line">│   │   ├── data.tf</span><br><span class="line">│   │   ├── compute_instance.tf</span><br><span class="line">│   │   └── versions.tf</span><br><span class="line">│   └── tokyo</span><br><span class="line">├── production</span><br><span class="line">│   ├── common</span><br><span class="line">│   ├── ...</span><br><span class="line">└── staging</span><br><span class="line">    ├── common</span><br><span class="line">    ├── ...</span><br></pre></td></tr></table></figure>

<p>各環境の中にcommonとリージョン毎のディレクトリを持ちます。commonにはVPCやプロジェクトといった共通となるリソースを置き、リージョン毎に必要なリソースはリージョンディレクトリに配置します。<br>また、各ディレクトリでbackendを持ち、tfstateを管理する形となるため<code>terraform</code>コマンドは各ディレクトリに対して行う必要があります。</p>
<p>単純にディレクトリをコピー&amp;ペーストして展開していけるので、リージョンの追加があった場合でも共通リソースの展開であれば容易に実行できます。視覚的にもしっかり分かれているので、新規参画者などにも認知負荷が高くないです。</p>
<p>ただ、共通リソースを参照する場合は<code>data</code>として用意する必要があり、冗長な感じは否めません。</p>
<figure class="highlight sh"><figcaption><span>terraform data.tf</span></figcaption><table><tr><td class="code"><pre><span class="line">data <span class="string">&quot;google_project&quot;</span> <span class="string">&quot;my_project&quot;</span> &#123;</span><br><span class="line">  project_id = <span class="string">&quot;ksst-bastion&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="構成案（2）：ディレクトリ分けとworkspaceを使って管理する">構成案（2）：ディレクトリ分けとworkspaceを使って管理する</h2><p>以下のように環境をそれぞれディレクトリ分けして管理します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">envs</span><br><span class="line">├── modules</span><br><span class="line">│   └── ...</span><br><span class="line">├── development</span><br><span class="line">│   ├── configs</span><br><span class="line">│   │   ├── sydney.tfvars</span><br><span class="line">│   │   └── tokyo.tfvars</span><br><span class="line">│   ├── backend.tf</span><br><span class="line">│   ├── compute_instance.tf</span><br><span class="line">│   ├── compute_network.tf</span><br><span class="line">│   ├── compute_subnetwork.tf</span><br><span class="line">│   ├── storage_bucket.tf</span><br><span class="line">│   ├── project.tf</span><br><span class="line">│   ├── variables.tf</span><br><span class="line">│   └── versions.tf</span><br><span class="line">├── production</span><br><span class="line">│   ├── configs</span><br><span class="line">│   │   ├── ...</span><br><span class="line">│   ├── backend.tf</span><br><span class="line">│   ...</span><br><span class="line">└── staging</span><br><span class="line">    ├── configs</span><br><span class="line">    │   ├── ...</span><br><span class="line">    ├── backend.tf</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure>

<p>先ほどとは異なり、各環境でbackendを1つとしてWorkspaceによってリージョンを区別していきます。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform workspace list</span></span><br><span class="line">  default</span><br><span class="line">* sydney</span><br><span class="line">  tokyo</span><br></pre></td></tr></table></figure>

<p>また、plan&#x2F;apply時にtfvarsを利用することによって各環境ワンリソースで管理することが可能となります。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">terraform plan -var-file config/sydney.tfvars</span><br><span class="line">terraform plan -var-file config/sydney.tfvars</span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-n7pbvx-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-n7pbvx-1" title="コードの折り返しを切り替える"></label><figcaption><span>terraform storage_bucket.tf</span></figcaption><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;google_storage_bucket&quot;</span> <span class="string">&quot;bucket&quot;</span> &#123;</span><br><span class="line">  project       = google_project.my_project.project_id</span><br><span class="line">  name          = <span class="string">&quot;<span class="variable">$&#123;var.region_short&#125;</span>-bucket-test&quot;</span></span><br><span class="line">  location      = var.region</span><br><span class="line">  force_destroy = <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>Terraform Workspaceは機能として存在するものの、開発環境を区別するのには非推奨<sup id="fnref:2">2</sup>など、中々使いどころの難しい存在でしたがリージョンを区別するのには使えそうです。この構成であれば、他リージョン展開時に新しい<code>tfvars</code>ファイルを作成するだけでよいので、ロールアウト時の作業が激減します。</p>
<p>ただ、新しいリソース・変数を定義する場合には全<code>tfvars</code>ファイルに値の追加が必要なので注意が必要です。</p>
<p>ちょっとした亜種ですが、Workspace名をそのまま変数として持ってきてリソースに適用もできます。<br><strong>※この場合は、Workspace名に<code>asia-northeast1</code>や<code>australia-southeast1</code>を使う必要があります。</strong></p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-n7pbvx-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-n7pbvx-2" title="コードの折り返しを切り替える"></label><figcaption><span>storage_bucket.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;google_storage_bucket&quot;</span> <span class="string">&quot;bucket&quot;</span> {
...
  location      = <span class="keyword">terraform</span>.workspace
}</pre></td></tr></tbody></table></figure></div>

<h2 id="構成案（3）：ワンリソースにしてtfvarsで管理する">構成案（3）：ワンリソースにしてtfvarsで管理する</h2><p>以下のようなディレクトリ構成で管理します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">envs</span><br><span class="line">├── modules</span><br><span class="line">│   └── ...</span><br><span class="line">├── configs</span><br><span class="line">│   ├── development</span><br><span class="line">│   │   ├── sydney.tfvars</span><br><span class="line">│   │   └── tokyo.tfvars</span><br><span class="line">│   ├── production</span><br><span class="line">│   │   ├── sydney.tfvars</span><br><span class="line">│   │   └── tokyo.tfvars</span><br><span class="line">│   └── staging</span><br><span class="line">│       ├── sydney.tfvars</span><br><span class="line">│       └── tokyo.tfvars</span><br><span class="line">├── backend.tf</span><br><span class="line">├── compute_instance.tf</span><br><span class="line">├── compute_network.tf</span><br><span class="line">├── compute_subnetwork.tf</span><br><span class="line">├── data.tf</span><br><span class="line">├── project.tf</span><br><span class="line">├── storage_bucket.tf</span><br><span class="line">├── variable.tf</span><br><span class="line">├── versions.tf</span><br><span class="line">└── terraform_init.sh</span><br></pre></td></tr></table></figure>

<p>各種リソースはワンリソースとして、Plan&#x2F;Apply時に各<code>tfvars</code>ファイルで変数を渡す形となります。<br>環境やリージョンを変更する場合のbackendの変更はどうするのか？ という部分ですが、<code>terraform_init.sh</code>というbashスクリプトを介して、各環境ごとでbackendを構成し直します。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-n7pbvx-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-n7pbvx-3" title="コードの折り返しを切り替える"></label><figcaption><span>terraform_init.sh</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="function"><span class="title">usage</span></span> () &#123;</span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;Usage: <span class="variable">$0</span> [option ...] [arg ...]&quot;</span></span><br><span class="line">  <span class="built_in">cat</span> &lt;&lt;<span class="string">&quot;EOM&quot;</span></span><br><span class="line">Options:</span><br><span class="line">  -h: Show this <span class="built_in">help</span></span><br><span class="line">  -c: city: Specify region<span class="string">&#x27;s city name</span></span><br><span class="line"><span class="string">  -e: environment: Specify enviroment</span></span><br><span class="line"><span class="string">EOM</span></span><br><span class="line"><span class="string">  exit 1</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">while getopts c:e:o:a option ; do</span></span><br><span class="line"><span class="string">  case $option in</span></span><br><span class="line"><span class="string">    c)</span></span><br><span class="line"><span class="string">      city=$OPTARG</span></span><br><span class="line"><span class="string">      echo &quot;City of region: $city&quot;</span></span><br><span class="line"><span class="string">      ;;</span></span><br><span class="line"><span class="string">    e)</span></span><br><span class="line"><span class="string">      env=$OPTARG</span></span><br><span class="line"><span class="string">      echo &quot;Environment: $env&quot;</span></span><br><span class="line"><span class="string">      ;;</span></span><br><span class="line"><span class="string">    h | \?)</span></span><br><span class="line"><span class="string">      echo &quot;-h or invalid option is used (OPTIND: $OPTIND)&quot;</span></span><br><span class="line"><span class="string">      usage</span></span><br><span class="line"><span class="string">      ;;</span></span><br><span class="line"><span class="string">  esac</span></span><br><span class="line"><span class="string">done</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"># Create backend.tf</span></span><br><span class="line"><span class="string">cat &lt;&lt;EOF &gt; backend.tf</span></span><br><span class="line"><span class="string">terraform &#123;</span></span><br><span class="line"><span class="string">  backend &quot;gcs&quot; &#123;</span></span><br><span class="line"><span class="string">    bucket = &quot;$&#123;env&#125;-multi-region-rollout-tfstate&quot;</span></span><br><span class="line"><span class="string">    prefix = &quot;$&#123;city&#125;/state&quot;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"># Initialize terraform&#x27;</span>s backend</span><br><span class="line">terraform init -reconfigure</span><br></pre></td></tr></table></figure></div>

<p>そのため、リージョン毎のbackend変更に関しては都度スクリプトを実行する運用でカバーしていく形となります。</p>
<p>以下の記事で紹介されているようにplan&#x2F;applyなどTerraformのコマンドもラッピングすることで、Terraformの操作を全てシェルスクリプト経由にしてしまったほうが誤ったbackendでのplan&#x2F;applyが起きないかもしれません。<br>参考：Terraformでmoduleを使わずに複数環境を構築する</p>
<h2 id="まとめ">まとめ</h2><p>上記3つの方法をまとめると以下のようになります。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 案 / リージョン展開方法 / リージョン毎の運用 / 新しいリソースの追加方法 / 認知負荷"><table>
<thead>
<tr>
<th>案</th>
<th>リージョン展開方法</th>
<th>リージョン毎の運用</th>
<th>新しいリソースの追加方法</th>
<th>認知負荷</th>
</tr>
</thead>
<tbody><tr>
<td>（1）ディレクトリ分けのみ</td>
<td>ディレクトリを追加</td>
<td>ディレクトリ毎でApply</td>
<td>各環境・リージョンでファイル追加</td>
<td>低</td>
</tr>
<tr>
<td>（2）ディレクトリ分けとworkspace</td>
<td>各環境でtfvarsとWorkspaceを追加</td>
<td>Workspace毎でApply</td>
<td>各環境でファイル追加</td>
<td>中</td>
</tr>
<tr>
<td>（3）ワンリソースにしてtfvars</td>
<td>各環境でtfvarsを追加</td>
<td>シェルスクリプトを実行してからApply</td>
<td>1ファイル追加</td>
<td>高</td>
</tr>
</tbody></table></div>
<p>展開するリージョンの数が少なければ（1）が楽そうですが、どんどん増えていくのであれば（2）と（3）のどちらかな気がします。</p>
<p>実務上では参画当初から（3）の方法で運用を回しており、CIでデプロイの自動化をしている部分もあるのでシェルスクリプトの実行に関してはそこまで負担に感じておりません。ただ、個人的には（2）のやり方がよりシンプルになるので好みです。</p>
<p>本記事ではインフラの多国展開時におけるTerraformの管理方法を紹介してみました。これらが正解というわけではなく、運用を回していく中で強み・弱みが見えてくると思いますので、せひチームに適した形を探してみてください。</p>
<p>アイキャッチ画像のアイコンは以下から引用させて頂いております。<br>HashiCorp Brand<br>Google Cloud - アーキテクチャ図用のプロダクト アイコン</p>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">リージョン名を都市・州名で表していますが、正式名称のasia-northeast1とかでも良いです。</span> ↩</li><li id="fn:2"><span style="vertical-align: top; padding-right: 10px;">2.</span><span style="vertical-align: top;">https://developer.hashicorp.com/terraform/cli/workspaces#when-not-to-use-multiple-workspaces</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">IaCを利用してインフラを構成することで、構築忘れや設定ミスといったイージーなミスが減らせるようになりました。とはいえ、展開していく範囲が増えれば増えるほどコードの量も増えていくので、このリソースはどこで作ったっけ…みたいなことが起きてしまいます。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="マルチリージョン" scheme="https://future-architect.github.io/tags/%E3%83%9E%E3%83%AB%E3%83%81%E3%83%AA%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3/"/>
  </entry>
  <entry>
    <title>Terraform連載2024 Stateを統合してみる</title>
    <link href="https://future-architect.github.io/articles/20240314a/"/>
    <id>https://future-architect.github.io/articles/20240314a/</id>
    <published>2024-03-13T15:00:00.000Z</published>
    <updated>2024-03-13T15:00:00.000Z</updated>
    <author><name>原田達也</name></author>
    <content type="html"><![CDATA[<p>Terraform 連載2024の4本目です。</p>
<h2 id="はじめに">はじめに</h2><p>はじめまして。Technology Inovation Group (TIG) の原田と申します。</p>
<p>私が所属しているチームではTerraformでAWSリソースを管理しており、tfstateを統合する機会があったので、その経験をもとに記事を書きます。</p>
<h3 id="環境">環境</h3><p>Terraform バージョン 1.5.7</p>
<h2 id="ことの経緯">ことの経緯</h2><p>私が所属しているチームでは、サービスごとに複数のtfstateでリソースを管理していました。</p>
<p>ただ、プロダクトが成熟してきて、リリース頻度が非常に低い&amp;リリースをしたとしても複数サービスが同じタイミングでリリースされることが多くなりました。</p>
<p>そういった理由から、成熟しきったStateを統合しようという機運が高まりました。</p>
<h2 id="方針">方針</h2><p>ゴールは、移行元のtfstate(a.tfstate)を移行先のtfstate(b.tfstate)にマージしてb.tfstateですべてのAWSリソースを管理できる状態です。</p>
<p>そのための作業パターンとして大きく2つの案を考えました。</p>
<h2 id="二つの作業パターン">二つの作業パターン</h2><h3 id="パターン（1）「import-removeコマンド」">パターン（1）「import &amp; removeコマンド」</h3><p>手順:</p>
<ol>
<li>統合元のStateで <code>remove</code> コマンド</li>
<li>統合先のStateで <code>import</code> コマンド</li>
<li>Terraform のコードを統合</li>
<li><code>plan</code> にて差分が発生しないことを確認</li>
</ol>
<p>お手軽ですね。Terraform直々に提供している機能ということもあって、安心感もあります。</p>
<p>1. 統合元のStateで <code>remove</code> コマンド</p>
<figure class="highlight console"><figcaption><span>s3の場合</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">統合元のStateにて</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform state <span class="built_in">rm</span> aws_s3_bucket.s3</span></span><br></pre></td></tr></table></figure>

<p>2. 統合先のStateで <code>import</code> コマンド</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-18hy68l-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-18hy68l-1" title="コードの折り返しを切り替える"></label><figcaption><span>s3の場合</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">統合先のStateにて</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform state import aws_s3_bucket.s3 バケット名</span></span><br></pre></td></tr></table></figure></div>

<p>Terraformのバージョンが1.5以上の場合はimport ブロックを使用するのもよいでしょう。</p>
<p>https://developer.hashicorp.com/terraform/language/import</p>
<p>3. Terraform のコードを統合<br>コピー&amp;ペーストでいいですね。リソースが多い場合はかなり根気が要ります。</p>
<p>4. <code>plan</code> にて差分が発生しないことを確認</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-18hy68l-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-18hy68l-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform plan</span></span><br><span class="line"></span><br><span class="line">==(略)==</span><br><span class="line"></span><br><span class="line">No changes. Your infrastructure matches the configuration.</span><br></pre></td></tr></table></figure></div>

<p>お疲れ様でした。</p>
<p>お気づきの方もいらっしゃるかと思いますが、管理リソースが多い場合はかなり作業コストが高いです。リソースごとに、import・removed・コードの修正が必要になります。</p>
<p>私のチームでは100を優に超えるAWSリソースを管理していたため、この方法は現実的ではありませんでした。</p>
<h3 id="パターン（2）「Stateを編集」">パターン（2）「Stateを編集」</h3><p>Stateを直接操作することはあまり推奨されないですが、今回はこの選択をとりました。</p>
<p>操作イメージとしては以下のような手順です。</p>
<img fetchpriority="high" src="/images/2024/20240314a/terraform_merge.drawio.png" alt="terraform_merge.drawio.png" width="427" height="429">

<p>手順:</p>
<ol>
<li>ローカルにtfstateをローカルに取得</li>
<li>2つのStateファイルをマージ</li>
<li>マージしたtfstateファイルを統合先にpush</li>
<li>Terraform のコードを統合</li>
<li><code>plan</code> にて差分が発生しないことを確認</li>
</ol>
<p>パターン（1）よりステップが1つ多くなっているように見えますが、ちょっと待ってください。詳しく見ていきましょう。</p>
<p>1. ローカルにtfstateをローカルに取得</p>
<figure class="highlight console"><figcaption><span>作業例</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">統合元のStateにて</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform state pull &gt; a.tfstate</span></span><br></pre></td></tr></table></figure>

<figure class="highlight console"><figcaption><span>作業例</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">統合先のStateにて</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform state pull &gt; b.tfstate</span></span><br></pre></td></tr></table></figure>

<p><strong>2. 2つのStateファイルをマージ</strong></p>
<p>ここが今回の作業のキモです。頑張って手作業でStateファイルをマージしていきましょう。</p>
<p>…というのは危険なので、今回はfujiwaraさんが開発された、便利なツールを利用させていただきましょう。</p>
<p>https://github.com/fujiwara/tfstate-merge</p>
<p>コマンド一撃でtfstateファイルをマージしてくれる、何ともありがたいツールです。</p>
<p>ある程度のバリデーション機能も実装されているので、手動でマージするより、格段に安心して作業できます。</p>
<p>こちらで作者であるfujiwaraさんの解説があるので、詳細に関してはそちらをぜひご覧ください。</p>
<p>https://sfujiwara.hatenablog.com/entry/tfstate-merge</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-18hy68l-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-18hy68l-3" title="コードの折り返しを切り替える"></label><figcaption><span>作業例</span></figcaption><table><tr><td class="code"><pre><span class="line">tfstate-merge b.tfstate a.tfstate &gt; b<span class="string">&#x27;.tfstate</span></span><br></pre></td></tr></table></figure></div>

<p>3. マージしたtfstateファイルを統合先にpush</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">terraform state push b<span class="string">&#x27;.tfstate</span></span><br></pre></td></tr></table></figure>

<div class="note-container note-warn"><span class="note-icon"></span><div>

<p>tfstateファイルは必ずバックアップを取っておくようにしましょう</p>
</div></div>

<p><strong>4. Terraform のコードを統合</strong></p>
<p>ここはパターン（1）と同様、根気よく作業していきましょう。<br>git の 差分の増減などを確認するもよいでしょう。</p>
<p>5. <code>plan</code> にて差分が発生しないことを確認</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-18hy68l-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-18hy68l-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform plan</span></span><br><span class="line"></span><br><span class="line">==(略)==</span><br><span class="line"></span><br><span class="line">No changes. Your infrastructure matches the configuration.</span><br></pre></td></tr></table></figure></div>

<p>お疲れ様でした。</p>
<p>こちらのパターンの利点は作業の負担が圧倒的に少ないです。ただ、<code>terraform state push</code>というかなり危険なコマンドを使用するので実行には細心の注意を払いましょう。</p>
<h2 id="おわりに">おわりに</h2><p>tfstateを統合した経験について書きました。</p>
<p>Stateの統廃合や、moduleへの移行など、Terraformのリファクタには様々な手札がありますが、エンジニアたるもの日常的により良いコードを目指していきたいですね。これは自身への戒めでもあります。</p>
<p>新卒でIT業界に飛び込み、もうすぐ2年が過ぎようとしていますが、自身の経験を発信していくことは良い刺激になりました。Terraform 連載2024の一環として執筆させていただきましたが、非常に興味深いトピックが目白押しなので、ぜひ他の記事もご覧ください！</p>
]]></content>
    <summary type="html">tfstateを統合する機会があったので、その経験をもとにまとめます。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="tfstate" scheme="https://future-architect.github.io/tags/tfstate/"/>
  </entry>
  <entry>
    <title>Terraform連載2024 Terraformにおける変数の制御について</title>
    <link href="https://future-architect.github.io/articles/20240313a/"/>
    <id>https://future-architect.github.io/articles/20240313a/</id>
    <published>2024-03-12T15:00:00.000Z</published>
    <updated>2024-03-12T15:00:00.000Z</updated>
    <author><name>森大作</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2024/20240313a/terraform_top.png" alt="" width="900" height="628">

<p>Terraform 連載2024の3本目です。</p>
<h2 id="はじめに">はじめに</h2><p>はじめまして。フューチャーキャリア入社1年半の森と申します。</p>
<p>Terraformにおける変数を、構築するインフラの要件に合わせてどのように制御していくかについてまとめます。</p>
<p>Terraformにおける変数制御は、構築するインフラの要件を明確化させる上で重要です。そのため、どのような変数制御があるか、ユースケースを踏まえ見ていきます。</p>
<h2 id="Terraformの変数制御">Terraformの変数制御</h2><p>Terraformには型による制約の他に、下記3種類の変数の制御方法があります。</p>
<ul>
<li>validation : 変数の<strong>静的な事前チェック</strong>を実施する</li>
<li>precondition(v1.2以降) : 変数の<strong>動的な事前チェック</strong>を実施する</li>
<li>postcondition(v1.2以降) : 変数の<strong>動的な事後チェック</strong>を実施する</li>
</ul>
<p>静的・動的の可不可や、事前・事後のチェックなどの違いがありますが、図にするとこのような感じです。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 動的チェック / 事前チェック / 事後チェック"><table>
<thead>
<tr>
<th></th>
<th>動的チェック</th>
<th>事前チェック</th>
<th>事後チェック</th>
</tr>
</thead>
<tbody><tr>
<td>validation</td>
<td>不可</td>
<td>可</td>
<td>不可</td>
</tr>
<tr>
<td>precondition</td>
<td>可</td>
<td>可</td>
<td>不可</td>
</tr>
<tr>
<td>postcondition</td>
<td>可</td>
<td>不可</td>
<td>可</td>
</tr>
</tbody></table></div>
<p>具体的に1つずつ確認していきましょう。</p>
<h2 id="validationについて">validationについて</h2><p><code>validation</code>は3つの変数制御の中でも最もシンプルで簡単な制御の方法です。</p>
<p>例えば、AWSのEC2でEBSを暗号化したい場合、以下のように<code>validation</code>ブロックを暗号化の有無に関する変数に追加します。</p>
<p>このブロックの<code>condition</code>の内容で真偽を判定し、偽の場合<code>error_message</code>で指定のエラーメッセージを出力できます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1801z9q-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-1" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;example&quot;</span> {
  ami           = <span class="string">&quot;ami-020283e959651b381&quot;</span>
  instance_type = <span class="string">&quot;t2.micro&quot;</span>
  subnet_id     = <span class="string">&quot;subnet-xxxxxxx&quot;</span>
<span class="line"></span>
  ebs_block_device {
    device_name = <span class="string">&quot;/dev/sdh&quot;</span>
    encrypted   = var.ebs_encryption
  }
}
<span class="line"></span>
<span class="keyword">variable</span> <span class="string">&quot;ebs_encryption&quot;</span> {
  type = bool
  validation {
    condition     = var.ebs_encryption == true
    error_message = <span class="string">&quot;EBS Should Be Encrypted&quot;</span>
  }
  description = <span class="string">&quot;A Boolean of EBS Encryption in the EC2 Instances&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<p><code>ebs_encryption</code>に<code>false</code>を指定した場合、<code>terraform plan</code>時に<code>validation</code>ブロックで指定した下記のエラーメッセージが出されます。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1801z9q-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">╷</span><br><span class="line">│ Error: Invalid value <span class="keyword">for</span> variable</span><br><span class="line">│</span><br><span class="line">│   on variables.tf line 1:</span><br><span class="line">│    1: variable <span class="string">&quot;ebs_encryption&quot;</span> &#123;</span><br><span class="line">│     ├────────────────</span><br><span class="line">│     │ var.ebs_encryption is <span class="literal">false</span></span><br><span class="line">│</span><br><span class="line">│ EBS Should Be Encrypted</span><br><span class="line">│</span><br><span class="line">│ This was checked by the validation rule at variables.tf:3,3-13.</span><br></pre></td></tr></table></figure></div>

<p>このように<code>condition</code>に条件式を書いておくことで、誤ってEBSの暗号化を無効にする事のないように事前にチェックをしてくれるのが<code>validation</code>の特徴です。</p>
<h3 id="validationのユースケース">validationのユースケース</h3><p>構築したいインフラに対して、あらかじめ変数の条件をハードコードすることで制約を課したい場合に<code>validation</code>ブロックが使えます。</p>
<h2 id="preconditionについて">preconditionについて</h2><p>上述の<code>validation</code>で課すことのできる制約は静的な条件に限りました。つまり、全て条件式にハードコードして制御する必要があり、動的にAWSから情報を取得して条件を絞ることは不可能でした。</p>
<p>これを解決したのがTerraform v1.2から追加された<code>precondition</code>です。例えば、EC2のインスタンスタイプを無料利用枠のみに制限したい場合、<code>aws_ec2_instance_type</code>データソースから最新の無料利用枠の情報をAWSから取得し、インスタンス構築の際の条件に課すことが可能です。</p>
<p><code>lifecycle</code>ブロック内に<code>precondition</code>ブロックを作成し、<code>condition</code>の条件で真偽を判定し、偽の場合に<code>error_message</code>に記載されているメッセージを出力できます。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1801z9q-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-3" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">data</span> <span class="string">&quot;aws_ec2_instance_type&quot;</span> <span class="string">&quot;example&quot;</span> {
  instance_type = <span class="string">&quot;c5.xlarge&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;example&quot;</span> {
  ami           = <span class="string">&quot;ami-0f7b55661ecbbe44c&quot;</span>
  instance_type = <span class="keyword">data</span>.aws_ec2_instance_type.example.instance_type
  subnet_id     = <span class="string">&quot;subnet-xxxxxxxxxx&quot;</span>
  lifecycle {
    precondition {
      condition     = <span class="keyword">data</span>.aws_ec2_instance_type.example.free_tier_eligible
      error_message = <span class="string">&quot;This instance type is not free in AWS&quot;</span>
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<p>上記のようにインスタンスタイプを<code>c5.xlarge</code>(無料利用枠でないインスタンスタイプ)でplanを流してみるとどうなるでしょうか？</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1801z9q-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">╷</span><br><span class="line">│ Error: Resource precondition failed</span><br><span class="line">│</span><br><span class="line">│   on main.tf line 18, <span class="keyword">in</span> resource <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;example&quot;</span>:</span><br><span class="line">│   18:       condition     = data.aws_ec2_instance_type.example.free_tier_eligible</span><br><span class="line">│     ├────────────────</span><br><span class="line">│     │ data.aws_ec2_instance_type.example.free_tier_eligible is <span class="literal">false</span></span><br><span class="line">│</span><br><span class="line">│ This instance <span class="built_in">type</span> is not free <span class="keyword">in</span> AWS</span><br></pre></td></tr></table></figure></div>

<p>指定のエラーメッセージとともに無事？ planが通らなくなりました。(当然、applyもできません。)</p>
<p>このように<code>validation</code>と違って動的な変数の制御を可能にするのが<code>precondition</code>の特徴です。</p>
<h3 id="preconditionのユースケース">preconditionのユースケース</h3><p><code>validation</code>でハードコーディングするのが難しい場合に使うのが良いでしょう。</p>
<p>最新のAWSからの情報が常に反映されるため、より安定的なチェックが実施できます。</p>
<h2 id="postconditionについて">postconditionについて</h2><p><code>varidation</code>や<code>precondition</code>はapply前に変数をチェックする<strong>事前チェック</strong>でした。一方で<code>postcondition</code>は、<strong>applyの後にエラーを追跡</strong>する事後チェックが可能です。</p>
<p>例としてオートスケーリンググループを作成する際、AZが２つ以上あるかどうかをチェックしたい場合を考えます。書き方は<code>precondition</code>と同様で、<code>postcondition</code>ブロックの<code>condition</code>でAZの数が1よりも大きくない場合に指定のエラーメッセージを出力するようにします。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1801z9q-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-5" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_launch_configuration&quot;</span> <span class="string">&quot;example&quot;</span> {
  image_id      = <span class="string">&quot;ami-020283e959651b381&quot;</span>
  instance_type = <span class="string">&quot;t2.micro&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_autoscaling_group&quot;</span> <span class="string">&quot;example&quot;</span> {
  name                 = <span class="string">&quot;ASG&quot;</span>
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier  = [<span class="string">&quot;subnet-xxxxxxx&quot;</span>]
  min_size             = <span class="number">1</span>
  max_size             = <span class="number">1</span>
<span class="line"></span>
  lifecycle {
    postcondition {
      condition     = length(self.availability_zones) &gt; <span class="number">1</span>
      error_message = <span class="string">&quot;You need to choose more than 1 AZ to ensure high availability&quot;</span>
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<p>planをしてみましょう。この時点でAZは<code>known after apply</code>とあるので、エラーは捕捉されずにplan自体は通ります。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1801z9q-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Terraform will perform the following actions:</span><br><span class="line"></span><br><span class="line">  <span class="comment"># aws_autoscaling_group.example will be created</span></span><br><span class="line">  + resource <span class="string">&quot;aws_autoscaling_group&quot;</span> <span class="string">&quot;example&quot;</span> &#123;</span><br><span class="line">      + arn                              = (known after apply)</span><br><span class="line">      + availability_zones               = (known after apply)</span><br><span class="line">      + default_cooldown                 = (known after apply)</span><br><span class="line">      + desired_capacity                 = (known after apply)</span><br><span class="line">      + force_delete                     = <span class="literal">false</span></span><br><span class="line">      + force_delete_warm_pool           = <span class="literal">false</span></span><br><span class="line">      + health_check_grace_period        = 300</span><br><span class="line">      + health_check_type                = (known after apply)</span><br><span class="line">      + <span class="built_in">id</span>                               = (known after apply)</span><br><span class="line">      + ignore_failed_scaling_activities = <span class="literal">false</span></span><br><span class="line">      + launch_configuration             = (known after apply)</span><br><span class="line">      + load_balancers                   = (known after apply)</span><br><span class="line">      + max_size                         = 1</span><br><span class="line">      + metrics_granularity              = <span class="string">&quot;1Minute&quot;</span></span><br><span class="line">      + min_size                         = 1</span><br><span class="line">      + name                             = <span class="string">&quot;ASG&quot;</span></span><br><span class="line">      + name_prefix                      = (known after apply)</span><br><span class="line">      + predicted_capacity               = (known after apply)</span><br><span class="line">      + protect_from_scale_in            = <span class="literal">false</span></span><br><span class="line">      + service_linked_role_arn          = (known after apply)</span><br><span class="line">      + target_group_arns                = (known after apply)</span><br><span class="line">      + vpc_zone_identifier              = [</span><br><span class="line">          + <span class="string">&quot;subnet-xxxxxxx&quot;</span>,</span><br><span class="line">        ]</span><br><span class="line">      + wait_for_capacity_timeout        = <span class="string">&quot;10m&quot;</span></span><br><span class="line">      + warm_pool_size                   = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment"># aws_launch_configuration.example will be created</span></span><br><span class="line">  + resource <span class="string">&quot;aws_launch_configuration&quot;</span> <span class="string">&quot;example&quot;</span> &#123;</span><br><span class="line">      + arn                         = (known after apply)</span><br><span class="line">      + associate_public_ip_address = (known after apply)</span><br><span class="line">      + ebs_optimized               = (known after apply)</span><br><span class="line">      + enable_monitoring           = <span class="literal">true</span></span><br><span class="line">      + <span class="built_in">id</span>                          = (known after apply)</span><br><span class="line">      + image_id                    = <span class="string">&quot;ami-020283e959651b381&quot;</span></span><br><span class="line">      + instance_type               = <span class="string">&quot;t2.micro&quot;</span></span><br><span class="line">      + key_name                    = (known after apply)</span><br><span class="line">      + name                        = (known after apply)</span><br><span class="line">      + name_prefix                 = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">Plan: 2 to add, 0 to change, 0 to destroy.</span><br></pre></td></tr></table></figure></div>

<p>この状態でapplyすると完了後に下記のエラーが出ます。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1801z9q-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">╷</span><br><span class="line">│ Error: Resource postcondition failed</span><br><span class="line">│</span><br><span class="line">│   on main.tf line 38, <span class="keyword">in</span> resource <span class="string">&quot;aws_autoscaling_group&quot;</span> <span class="string">&quot;example&quot;</span>:</span><br><span class="line">│   38:       condition     = length(self.availability_zones) &gt; 1</span><br><span class="line">│     ├────────────────</span><br><span class="line">│     │ self.availability_zones is <span class="built_in">set</span> of string with 1 element</span><br><span class="line">│</span><br><span class="line">│ You need to choose more than 1 AZ to ensure high availability</span><br></pre></td></tr></table></figure></div>

<p>エラーは出力されていますが、<code>precondition</code>と異なり、planはちゃんと通ってリソースまで作成されているのが分かります。</p>
<p>これが<code>postcondition</code>の事後チェックというもので、apply後へのリソースの変数に対するチェックを実施することが可能になっています。</p>
<h3 id="postconditionのユースケース">postconditionのユースケース</h3><p><code>validation</code>や<code>precondition</code>などの事前チェックのみで補足しきれない条件を課すのが良いでしょう。</p>
<p>上記のような例だと、apply時にリソースが作成されてしまうので、事前チェックのようにリソース作成前に制限を課すような強い制約ができないことには注意が必要です。</p>
<h2 id="おまけ">おまけ</h2><p>インスタンスタイプの選定で<code>precondition</code>を使いましたが、<code>precondition</code>→<code>postcondition</code>と単純に置き換えてみたらどうなるでしょうか？</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-1801z9q-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-8" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="keyword">data</span> <span class="string">&quot;aws_ec2_instance_type&quot;</span> <span class="string">&quot;example&quot;</span> {
  instance_type = <span class="string">&quot;c5.xlarge&quot;</span>
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;example&quot;</span> {
  ami           = <span class="string">&quot;ami-0f7b55661ecbbe44c&quot;</span>
  instance_type = <span class="keyword">data</span>.aws_ec2_instance_type.example.instance_type
  subnet_id     = <span class="string">&quot;subnet-xxxxxxx&quot;</span>
  lifecycle {
    postcondition {
      condition     = <span class="keyword">data</span>.aws_ec2_instance_type.example.free_tier_eligible
      error_message = <span class="string">&quot;This instance type is not free in AWS&quot;</span>
    }
  }
}</pre></td></tr></tbody></table></figure></div>

<p>上記はpreconditionについてにおけるコードで<code>precondition</code>が<code>postcondition</code>に置き換わっているだけです。</p>
<p>planで下記エラーが出てきます。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1801z9q-9" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1801z9q-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:</span><br><span class="line">  + create</span><br><span class="line"></span><br><span class="line">Terraform planned the following actions, but <span class="keyword">then</span> encountered a problem:</span><br><span class="line"></span><br><span class="line">  <span class="comment"># aws_instance.example will be created</span></span><br><span class="line">  + resource <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;example&quot;</span> &#123;</span><br><span class="line">      + ami                                  = <span class="string">&quot;ami-0f7b55661ecbbe44c&quot;</span></span><br><span class="line">      + arn                                  = (known after apply)</span><br><span class="line">      + associate_public_ip_address          = (known after apply)</span><br><span class="line">      + availability_zone                    = (known after apply)</span><br><span class="line">      + cpu_core_count                       = (known after apply)</span><br><span class="line">      + cpu_threads_per_core                 = (known after apply)</span><br><span class="line">      + disable_api_stop                     = (known after apply)</span><br><span class="line">      + disable_api_termination              = (known after apply)</span><br><span class="line">      + ebs_optimized                        = (known after apply)</span><br><span class="line">      + get_password_data                    = <span class="literal">false</span></span><br><span class="line">      + host_id                              = (known after apply)</span><br><span class="line">      + host_resource_group_arn              = (known after apply)</span><br><span class="line">      + iam_instance_profile                 = (known after apply)</span><br><span class="line">      + <span class="built_in">id</span>                                   = (known after apply)</span><br><span class="line">      + instance_initiated_shutdown_behavior = (known after apply)</span><br><span class="line">      + instance_lifecycle                   = (known after apply)</span><br><span class="line">      + instance_state                       = (known after apply)</span><br><span class="line">      + instance_type                        = <span class="string">&quot;c5.xlarge&quot;</span></span><br><span class="line">      + ipv6_address_count                   = (known after apply)</span><br><span class="line">      + ipv6_addresses                       = (known after apply)</span><br><span class="line">      + key_name                             = (known after apply)</span><br><span class="line">      + monitoring                           = (known after apply)</span><br><span class="line">      + outpost_arn                          = (known after apply)</span><br><span class="line">      + password_data                        = (known after apply)</span><br><span class="line">      + placement_group                      = (known after apply)</span><br><span class="line">      + placement_partition_number           = (known after apply)</span><br><span class="line">      + primary_network_interface_id         = (known after apply)</span><br><span class="line">      + private_dns                          = (known after apply)</span><br><span class="line">      + private_ip                           = (known after apply)</span><br><span class="line">      + public_dns                           = (known after apply)</span><br><span class="line">      + public_ip                            = (known after apply)</span><br><span class="line">      + secondary_private_ips                = (known after apply)</span><br><span class="line">      + security_groups                      = (known after apply)</span><br><span class="line">      + source_dest_check                    = <span class="literal">true</span></span><br><span class="line">      + spot_instance_request_id             = (known after apply)</span><br><span class="line">      + subnet_id                            = <span class="string">&quot;subnet-xxxxxxx&quot;</span></span><br><span class="line">      + tags_all                             = (known after apply)</span><br><span class="line">      + tenancy                              = (known after apply)</span><br><span class="line">      + user_data                            = (known after apply)</span><br><span class="line">      + user_data_base64                     = (known after apply)</span><br><span class="line">      + user_data_replace_on_change          = <span class="literal">false</span></span><br><span class="line">      + vpc_security_group_ids               = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">Plan: 1 to add, 0 to change, 0 to destroy.</span><br><span class="line">╷</span><br><span class="line">│ Error: Resource postcondition failed</span><br><span class="line">│</span><br><span class="line">│   on main.tf line 11, <span class="keyword">in</span> resource <span class="string">&quot;aws_instance&quot;</span> <span class="string">&quot;example&quot;</span>:</span><br><span class="line">│   11:       condition     = data.aws_ec2_instance_type.example.free_tier_eligible</span><br><span class="line">│     ├────────────────</span><br><span class="line">│     │ data.aws_ec2_instance_type.example.free_tier_eligible is <span class="literal">false</span></span><br><span class="line">│</span><br><span class="line">│ This instance <span class="built_in">type</span> is not free <span class="keyword">in</span> AWS</span><br></pre></td></tr></table></figure></div>

<p>エラーメッセージは<code>precondition</code>とほぼ変わらないのですが、<code>postcondition</code>では plan によるリソース出力が成功します（<code>precondition</code>ではplanが失敗してリソース出力がされませんでした）。</p>
<p>これは<code>postcondition</code>が事後チェックであることを反映している例で、データソースやリソースが取得された後で値が評価されていることを如実に表しています。</p>
<p>こうなると <code>postcondition</code>で全部チェックしてしまっても良い気もしますが、事後チェックであるかを明示するため、<code>precondition</code>で制御できる箇所は<code>precondition</code>で制御するようにしましょう。</p>
<h2 id="さいごに">さいごに</h2><p>多くの変数に対して制限を課すのはなかなか大変で工数を取る上、可読性にも影響します。</p>
<p>修正の際にupdate in placeなどで気軽にアップデート出来ない変数(EBS暗号化の有無など)に対する制約から優先的に実施するのが個人的には良いと思います。</p>
<h2 id="参考">参考</h2><ul>
<li>詳解Terraform</li>
</ul>
]]></content>
    <summary type="html">Terraformにおける変数を、構築するインフラの要件に合わせてどのように制御していくかについてお話していきたいと思います。Terraformにおける変数制御は、構築するインフラの要件を明確化させる上で重要になってきます。そこで今回はどんな変数制御があるか、ユースケースを踏まえて見ていきたいと思います。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
  </entry>
  <entry>
    <title>Terraform連載2024 hclwriteを用いたtfコード生成入門</title>
    <link href="https://future-architect.github.io/articles/20240312a/"/>
    <id>https://future-architect.github.io/articles/20240312a/</id>
    <published>2024-03-11T15:00:00.000Z</published>
    <updated>2024-03-11T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<p>Terraform 連載2024 の2本目の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>TIG真野です。</p>
<p>Terraformファイルをコード生成するため、hclwriteというGoパッケージの使い方を調べました。</p>
<h2 id="モチベーション">モチベーション</h2><p>ある複数のリソースをセットで定義する設計開発ルールがあったとします。AWSの例ですが、以下のようにDynamoDBとその監視をCloudwatch Metricsを用いてセットで行いたいとします。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-f8gtr9-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-f8gtr9-1" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre><span class="comment"># DynamoDB</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_dynamodb_table&quot;</span> <span class="string">&quot;myproduct_read&quot;</span> {
  name         = <span class="string">&quot;<span class="variable">${terraform.workspace}</span>-myproduct-read&quot;</span>
  billing_mode = <span class="string">&quot;PAY_PER_REQUEST&quot;</span>
  hash_key     = <span class="string">&quot;user_id&quot;</span>
  range_key    = <span class="string">&quot;content_id&quot;</span>
<span class="line"></span>
  deletion_protection_enabled = true
<span class="line"></span>
  attribute {
    name = <span class="string">&quot;user_id&quot;</span>
    type = <span class="string">&quot;S&quot;</span>
  }
  attribute {
    name = <span class="string">&quot;content_id&quot;</span>
    type = <span class="string">&quot;S&quot;</span>
  }
}
<span class="line"></span>
<span class="comment"># DynamoDBに対して、Cloudwatch Metricsで監視する。例では1件だが複数あるとする</span>
<span class="keyword">resource</span> <span class="string">&quot;aws_cloudwatch_metric_alarm&quot;</span> <span class="string">&quot;myproduct_read_dynamodb_throttledrequests&quot;</span> {
  alarm_name          = <span class="string">&quot;<span class="variable">${aws_dynamodb_table.myproduct_read.name}</span>-ThrottledRequests&quot;</span>
  comparison_operator = <span class="string">&quot;GreaterThanOrEqualToThreshold&quot;</span>
  datapoints_to_alarm = <span class="string">&quot;1&quot;</span>
  evaluation_periods  = <span class="string">&quot;1&quot;</span>
  metric_name         = <span class="string">&quot;ThrottledRequests&quot;</span>
  namespace           = <span class="string">&quot;AWS/DynamoDB&quot;</span>
  period              = <span class="string">&quot;60&quot;</span>
  statistic           = <span class="string">&quot;Maximum&quot;</span>
  threshold           = <span class="string">&quot;1&quot;</span>
  alarm_actions       = aws_sns_topic.myproduct_alert.arn
  dimensions = {
    TableName = aws_dynamodb_table.read.name
  }
}</pre></td></tr></tbody></table></figure></div>

<p>ここではスロットリング数だけを監視していますが、もう1~5個くらい監視したい項目があったとします。この例のように、あるリソースの追加に合わせて整合性を保ちつつ別のリソースを追加することは難しく、抜け漏れがちです。</p>
<p>正攻法だとTerraform module化でしょう。しかし、このケースではモジュール化するにしてはリソース数が少なく、モジュール化すること自体が新規参画した開発者にとって認知負荷が高いことを考えると、もう少しプロダクトが成長して、関連するリソースが増えるかどうかを待ってから対応を考えたいケースもあるでしょう。もちろんチームの方針としてこれくらいでもすぐにモジュール化に取り掛かる場合もあるかと思いますが、チームのTerraform習熟度にバラツキがあり設計パターンを抑えたいなど、様々な背景があったとします。</p>
<p>こうした場面で、あるTerraformで定義したリソース（ここではDynamoDB）をインプットに別のコード（Cloudwatch Metrics）を生成し、モジュール化しなくとも不整合が生じにくい開発フローを整備したいと思います。これでモジュール化の判断を先送りにできますね。</p>
<img fetchpriority="high" src="/images/2024/20240312a/dynamodb_hclwrite.drawio.png" alt="" width="1200" height="631">

<p>ちなみに、通常、DynamoDBはそこまで数が増えない（ほいほい増えるようであればおそらくDynamoDBを使うべきではない）し、監視項目もそう変更しないだろうから、コード生成もモジュール化しなくても良いんじゃないか？ という意見もあるかと思いますが、それはそれとします。</p>
<h2 id="整合性チェック">整合性チェック</h2><p>この回のケースではRego（Conftest）を使って整合性チェックを入れるのも有効でしょう。しかし開発チームにRego経験者はほとんど供給され無いと思うので、学習コストが多少なりとも掛かります。また、不整合を検知できるのであれば自動でFixしてくれた方が開発者フレンドリーです。</p>
<p>そのため、この記事では .tf ファイルの自動生成に注目します。</p>
<h2 id="利用パッケージ">利用パッケージ</h2><p>Terraformの .tf コードをパース、生成する方法として有名なのは、hashicorp&#x2F;hcl を用いることです。Go言語でインポートしてライブラリとして使えます。HCLはTerraformの.tfファイルが利用するファイルフォーマットのことです。</p>
<p>管理されているパッケージはいくつかあり、以下のようなものが含まれていて、いい感じに使い分けるリテラシーが求められます。</p>
<p>パッケージの概略はthaimさんのZenn記事のHCLファイルを hashicorp&#x2F;hcl で読み書きするが実装もありイメージしやすいです。</p>
<p>ここでも簡単に一覧を載せます。</p>
<div class="scroll" tabindex="0" role="region" aria-label="表 Name / Memo"><table>
<thead>
<tr>
<th>Name</th>
<th>Memo</th>
</tr>
</thead>
<tbody><tr>
<td>hclsimple</td>
<td>HCLをGoの構造体にマッピングする、encoding&#x2F;json 的な高レベルなパッケージです。しかし、拡張子.tf には対応しておらず、全ての.tfファイルに対応していないことを言外に伝えています。</td>
</tr>
<tr>
<td>hclparse</td>
<td>HCLファイルをパースして、結果を独自のStructで取得できます</td>
</tr>
<tr>
<td>hclwrite</td>
<td>HCLファイルを加工するのに適したペッケージです。元のHCLファイルの構造を壊さず、リソースの追加&#x2F;削除、コメントや属性などの編集を行えます</td>
</tr>
<tr>
<td>hclsyntax</td>
<td>HCLを解析してASTを作るパッケージです。hclparseなどにも使われています</td>
</tr>
</tbody></table></div>
<p>今回はTerraformコードの細かい解析は不要であるため、hclwriteパッケージを利用します。</p>
<h2 id="hclwrite-でファイル読み込み">hclwrite でファイル読み込み</h2><p>バージョンは <code>hashicorp/hcl/v2 v2.20.0</code> を利用します。</p>
<p>まずは.tfファイルを読み込みます。</p>
<p><code>hclwrite.ParseConfig()</code> でパースしたいファイルを指定します。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-f8gtr9-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-f8gtr9-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;os&quot;</span></span><br><span class="line"></span><br><span class="line">	<span class="string">&quot;github.com/hashicorp/hcl/v2&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/hashicorp/hcl/v2/hclwrite&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="keyword">if</span> <span class="built_in">len</span>(os.Args) == <span class="number">1</span> &#123;</span><br><span class="line">		log.Fatalf(<span class="string">&quot;Usage: %s &lt;filepath&gt;\n&quot;</span>, os.Args[<span class="number">0</span>])</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	hclFilePath := os.Args[<span class="number">1</span>]</span><br><span class="line">	file, err := os.ReadFile(os.Args[<span class="number">1</span>])</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Fatalf(<span class="string">&quot;Usage: %s &lt;filepath&gt;\n&quot;</span>, os.Args[<span class="number">1</span>])</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	tfFile, diags := hclwrite.ParseConfig(file, hclFilePath, hcl.Pos&#123;Line: <span class="number">1</span>, Column: <span class="number">1</span>&#125;)</span><br><span class="line">	<span class="keyword">if</span> diags != <span class="literal">nil</span> &amp;&amp; diags.HasErrors() &#123;</span><br><span class="line">		log.Fatalf(<span class="string">&quot;hclwrite parse: %s&quot;</span>, diags)</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">if</span> tfFile == <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Fatalf(<span class="string">&quot;parse result is nil: %s&quot;</span>, hclFilePath)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	blocks := tfFile.Body().Blocks()</span><br><span class="line">	referenceNames := <span class="built_in">make</span>([]<span class="type">string</span>, <span class="number">0</span>, <span class="built_in">len</span>(blocks))</span><br><span class="line">	<span class="keyword">for</span> _, b := <span class="keyword">range</span> blocks &#123;</span><br><span class="line">		<span class="keyword">if</span> b.Type() != <span class="string">&quot;resource&quot;</span> || b.Labels()[<span class="number">0</span>] != <span class="string">&quot;aws_dynamodb_table&quot;</span> &#123;</span><br><span class="line">			<span class="keyword">continue</span></span><br><span class="line">		&#125;</span><br><span class="line">		referenceNames = <span class="built_in">append</span>(referenceNames, b.Labels()[<span class="number">1</span>])</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	fmt.Println(referenceNames)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>結果の取得は <code>tfFile.Body().Blocks()</code> という部分で取得できます。</p>
<p>Blockとはなにかですが、 <code>resource</code>、<code>module</code>、<code>locals</code> のようなTerraform上でインデントをともなうような塊を指します。例えば、<code>resource &quot;aws_dynamodb_table&quot; &quot;table1&quot; &#123;...&#125;</code> といった定義が10あれば、for文が10呼ばれます。</p>
<p>次にわかりにくいのが <code>b.Labels()</code> の部分です。これは <code>aws_dynamodb_table</code>, <code>table1</code> といったブロックを開くときに設定されるTerraformのリソースタイプ、リソース名が入ります。</p>
<p>今回はTerraformのリソース名を取得して表示するとします。</p>
<p>次のようなファイルがあるとします。</p>
<div class="code-block"><figure class="highlight tf"><input type="checkbox" id="code-wrap-f8gtr9-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-f8gtr9-3" title="コードの折り返しを切り替える"></label><figcaption><span>dynamodb_table.tf</span></figcaption><table><tbody><tr><td class="code"><pre><span class="keyword">resource</span> <span class="string">&quot;aws_dynamodb_table&quot;</span> <span class="string">&quot;myproduct_read&quot;</span> {
  name         = <span class="string">&quot;<span class="variable">${terraform.workspace}</span>-myproduct-read&quot;</span>
  billing_mode = <span class="string">&quot;PAY_PER_REQUEST&quot;</span>
  hash_key     = <span class="string">&quot;user_id&quot;</span>
  range_key    = <span class="string">&quot;content_id&quot;</span>
<span class="line"></span>
  deletion_protection_enabled = true
<span class="line"></span>
  attribute {
    name = <span class="string">&quot;user_id&quot;</span>
    type = <span class="string">&quot;S&quot;</span>
  }
  attribute {
    name = <span class="string">&quot;content_id&quot;</span>
    type = <span class="string">&quot;S&quot;</span>
  }
}
<span class="line"></span>
<span class="keyword">resource</span> <span class="string">&quot;aws_dynamodb_table&quot;</span> <span class="string">&quot;myproduct_content&quot;</span> {
  name         = <span class="string">&quot;<span class="variable">${terraform.workspace}</span>-myproduct-read&quot;</span>
  billing_mode = <span class="string">&quot;PAY_PER_REQUEST&quot;</span>
  hash_key     = <span class="string">&quot;content_id&quot;</span>
<span class="line"></span>
  deletion_protection_enabled = true
<span class="line"></span>
  attribute {
    name = <span class="string">&quot;content_id&quot;</span>
    type = <span class="string">&quot;S&quot;</span>
  }
}</pre></td></tr></tbody></table></figure></div>

<p>さきほどのコードを実行すると、Terraformリソース名が取れています。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go run . ../example/dynamodb_table.tf</span></span><br><span class="line">[myproduct_read myproduct_content]</span><br></pre></td></tr></table></figure>

<p>属性を取得するためには、<code>b.Body().GetAttribute()</code> などで取得できますので、目的に応じて条件を追加できます。</p>
<h2 id="空リソース生成">空リソース生成</h2><p>次に.tfファイルを生成します。</p>
<p><code>hclwrite.NewFile()</code> で初期化し、そこにブロック（Terraformリソース）を追加していきます。</p>
<p>今回は新規コード生成なので、先頭に <code>// DO NOT EDIT</code> コメントを追加しましょう。</p>
<p>hclwriteを用いるとコメントを追加する便利な関数は（おそらく）存在しないので、いきなりですがトークンレベルの操作となる、<code>AppendUnstructedTokens()</code> を用います。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-f8gtr9-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-f8gtr9-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="comment">// 中略</span></span><br><span class="line"></span><br><span class="line">	newFile := hclwrite.NewFile()</span><br><span class="line">	newFile.Body().AppendUnstructuredTokens(hclwrite.Tokens&#123; <span class="comment">// 先頭行にコメント追加</span></span><br><span class="line">		&#123;</span><br><span class="line">			Type:  hclsyntax.TokenIdent,</span><br><span class="line">			Bytes: []<span class="type">byte</span>(<span class="string">&quot;// DO NOT EDIT, MADE BY hclwrite-dynamodb-generator\n&quot;</span>),</span><br><span class="line">		&#125;,</span><br><span class="line">	&#125;)</span><br><span class="line"></span><br><span class="line">	<span class="keyword">for</span> _, resourceName := <span class="keyword">range</span> referenceNames &#123;</span><br><span class="line">		newFile.Body().AppendUnstructuredTokens(hclwrite.Tokens&#123;</span><br><span class="line">			&#123;Type: hclsyntax.TokenNewline, Bytes: []<span class="type">byte</span>(<span class="string">&quot;\n&quot;</span>)&#125;, <span class="comment">// 先頭行に改行を入れる</span></span><br><span class="line">		&#125;)</span><br><span class="line">		labels := []<span class="type">string</span>&#123;<span class="string">&quot;aws_cloudwatch_metric_alarm&quot;</span>, fmt.Sprintf(<span class="string">&quot;dynamodb_throttledrequests_%s&quot;</span>, resourceName)&#125;</span><br><span class="line">		newFile.Body().AppendNewBlock(<span class="string">&quot;resource&quot;</span>, labels)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	out := hclwrite.Format(newFile.BuildTokens(<span class="literal">nil</span>).Bytes())</span><br><span class="line">	_, _ = fmt.Fprint(os.Stdout, <span class="type">string</span>(out))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>for 文の中にある、 <code>AppendNewBlock()</code> が今回出力したい本丸の、<code>aws_cloudwatch_metric_alarm</code> リソースを追加する部分です。</p>
<p>実行すると次のような空リソースが生成されます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-f8gtr9-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-f8gtr9-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go run . ../example/dynamodb_table_one.tf</span></span><br><span class="line">// DO NOT EDIT, MADE BY hclwrite-dynamodb-generator</span><br><span class="line"></span><br><span class="line">resource &quot;aws_cloudwatch_metric_alarm&quot; &quot;dynamodb_throttledrequests_myproduct_read&quot; &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_cloudwatch_metric_alarm&quot; &quot;dynamodb_throttledrequests_myproduct_content&quot; &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h2 id="属性の追加">属性の追加</h2><p><code>SetAttributeRaw()</code> を用いて各属性ごとに項目を追加していきます。 <code>SetAttributeRaw()</code> は低レベルのAPIで、トークンを直接追加します。今回、<code>&quot;$&#123;aws_dynamodb_table.myproduct_read.name&#125;-throttledrequests&quot;</code> といったリファレンスを追加したいため利用しています。<code>SetAttributeValue()</code> を使う方法だと、 <code>$</code> がエスケープされて、 <code>$$</code> と出力されてしまうためです。</p>
<p>また、特記したいことはzclconf&#x2F;go-cty というライブラリの型で値を競ってしないとならないことです。ここでさらに別のライブラリ？ と一瞬焦る気持ちがありますが、慣れていきましょう。</p>
<p>属性の型がオブジェクトであり、その中にリファレンスが入ると、再び <code>AppendNewBlock()</code> を呼び出す必要があるなど、生成したい定義によっては試行錯誤する必要があるので、注意してください。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-f8gtr9-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-f8gtr9-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="comment">// 中略</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> _, resourceName := <span class="keyword">range</span> referenceNames &#123;</span><br><span class="line">		newFile.Body().AppendUnstructuredTokens(hclwrite.Tokens&#123;</span><br><span class="line">			&#123;Type: hclsyntax.TokenNewline, Bytes: []<span class="type">byte</span>(<span class="string">&quot;\n&quot;</span>)&#125;, <span class="comment">// 先頭行に改行を入れる</span></span><br><span class="line">		&#125;)</span><br><span class="line">		labels := []<span class="type">string</span>&#123;<span class="string">&quot;aws_cloudwatch_metric_alarm&quot;</span>, fmt.Sprintf(<span class="string">&quot;dynamodb_throttledrequests_%s&quot;</span>, resourceName)&#125;</span><br><span class="line">		resource := newFile.Body().AppendNewBlock(<span class="string">&quot;resource&quot;</span>, labels).Body()</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 属性の定義</span></span><br><span class="line">		resource.SetAttributeRaw(<span class="string">&quot;alarm_name&quot;</span>, hclwrite.Tokens&#123;</span><br><span class="line">			&#123;</span><br><span class="line">				Type:  hclsyntax.TokenIdent,</span><br><span class="line">				Bytes: []<span class="type">byte</span>(<span class="string">`&quot;$&#123;aws_dynamodb_table.myproduct_read.name&#125;-throttledrequests&quot;`</span>),</span><br><span class="line">			&#125;,</span><br><span class="line">		&#125;)</span><br><span class="line">		resource.SetAttributeValue(<span class="string">&quot;comparison_operator&quot;</span>, cty.StringVal(<span class="string">&quot;GreaterThanOrEqualToThreshold&quot;</span>))</span><br><span class="line">		resource.SetAttributeValue(<span class="string">&quot;datapoints_to_alarm&quot;</span>, cty.StringVal(<span class="string">&quot;1&quot;</span>))</span><br><span class="line">		resource.SetAttributeValue(<span class="string">&quot;evaluation_periods&quot;</span>, cty.StringVal(<span class="string">&quot;1&quot;</span>))</span><br><span class="line">		resource.SetAttributeValue(<span class="string">&quot;metric_name&quot;</span>, cty.StringVal(<span class="string">&quot;ThrottledRequests&quot;</span>))</span><br><span class="line">		resource.SetAttributeValue(<span class="string">&quot;namespace&quot;</span>, cty.StringVal(<span class="string">&quot;AWS/DynamoDB&quot;</span>))</span><br><span class="line">		resource.SetAttributeValue(<span class="string">&quot;period&quot;</span>, cty.StringVal(<span class="string">&quot;60&quot;</span>))</span><br><span class="line">		resource.SetAttributeValue(<span class="string">&quot;statistic&quot;</span>, cty.StringVal(<span class="string">&quot;Maximum&quot;</span>))</span><br><span class="line">		resource.SetAttributeValue(<span class="string">&quot;threshold&quot;</span>, cty.StringVal(<span class="string">&quot;1&quot;</span>))</span><br><span class="line">		resource.SetAttributeTraversal(<span class="string">&quot;alarm_actions&quot;</span>, hcl.Traversal&#123;</span><br><span class="line">			hcl.TraverseRoot&#123;Name: <span class="string">&quot;aws_sns_topic&quot;</span>&#125;,</span><br><span class="line">			hcl.TraverseAttr&#123;Name: <span class="string">&quot;myproduct_alert&quot;</span>&#125;,</span><br><span class="line">			hcl.TraverseAttr&#123;Name: <span class="string">&quot;arn&quot;</span>&#125;,</span><br><span class="line">		&#125;)</span><br><span class="line">		dimensions := resource.AppendNewBlock(<span class="string">&quot;dimensions&quot;</span>, <span class="literal">nil</span>).Body()</span><br><span class="line">		dimensions.SetAttributeTraversal(<span class="string">&quot;TableName&quot;</span>, hcl.Traversal&#123;</span><br><span class="line">			hcl.TraverseRoot&#123;Name: <span class="string">&quot;aws_dynamodb_table&quot;</span>&#125;,</span><br><span class="line">			hcl.TraverseAttr&#123;Name: <span class="string">&quot;myproduct_read&quot;</span>&#125;,</span><br><span class="line">			hcl.TraverseAttr&#123;Name: <span class="string">&quot;name&quot;</span>&#125;,</span><br><span class="line">		&#125;)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	out := hclwrite.Format(newFile.BuildTokens(<span class="literal">nil</span>).Bytes())</span><br><span class="line">	_, _ = fmt.Fprint(os.Stdout, <span class="type">string</span>(out))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>これを実行すると次のようにTerraformコードが生成されます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-f8gtr9-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-f8gtr9-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go run . example/dynamodb_table_one.tf</span></span><br><span class="line">// DO NOT EDIT, MADE BY hclwrite-dynamodb-generator</span><br><span class="line"></span><br><span class="line">resource &quot;aws_cloudwatch_metric_alarm&quot; &quot;dynamodb_throttledrequests_myproduct_read&quot; &#123;</span><br><span class="line">  alarm_name          = &quot;$&#123;aws_dynamodb_table.myproduct_read.name&#125;-throttledrequests&quot;</span><br><span class="line">  comparison_operator = &quot;GreaterThanOrEqualToThreshold&quot;</span><br><span class="line">  datapoints_to_alarm = &quot;1&quot;</span><br><span class="line">  evaluation_periods  = &quot;1&quot;</span><br><span class="line">  metric_name         = &quot;ThrottledRequests&quot;</span><br><span class="line">  namespace           = &quot;AWS/DynamoDB&quot;</span><br><span class="line">  period              = &quot;60&quot;</span><br><span class="line">  statistic           = &quot;Maximum&quot;</span><br><span class="line">  threshold           = &quot;1&quot;</span><br><span class="line">  alarm_actions       = aws_sns_topic.myproduct_alert.arn</span><br><span class="line">  dimensions &#123;</span><br><span class="line">    TableName = aws_dynamodb_table.myproduct_read.name</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_cloudwatch_metric_alarm&quot; &quot;dynamodb_throttledrequests_myproduct_content&quot; &#123;</span><br><span class="line">  alarm_name          = &quot;$&#123;aws_dynamodb_table.myproduct_read.name&#125;-throttledrequests&quot;</span><br><span class="line">  comparison_operator = &quot;GreaterThanOrEqualToThreshold&quot;</span><br><span class="line">  datapoints_to_alarm = &quot;1&quot;</span><br><span class="line">  evaluation_periods  = &quot;1&quot;</span><br><span class="line">  metric_name         = &quot;ThrottledRequests&quot;</span><br><span class="line">  namespace           = &quot;AWS/DynamoDB&quot;</span><br><span class="line">  period              = &quot;60&quot;</span><br><span class="line">  statistic           = &quot;Maximum&quot;</span><br><span class="line">  threshold           = &quot;1&quot;</span><br><span class="line">  alarm_actions       = aws_sns_topic.myproduct_alert.arn</span><br><span class="line">  dimensions &#123;</span><br><span class="line">    TableName = aws_dynamodb_table.myproduct_read.name</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>hclwriteパッケージ自体は文法チェックを行いません。そのため　<code>alarm_actions = aws_sns_topic.myproduct_alert.arn</code> としれっと存在しないリソースを参照してもエラーにはなりません。</p>
<p>説明を省きましたが <code>hclwrite.Format()</code> でフォーマットをかけられるので、お手軽です。</p>
<p>今回使用したコードの全量は↓のリポジトリにコミットしています。</p>
<p>https://github.com/ma91n/hclwrite-dynamodb</p>
<h2 id="hclwriteパッケージを利用すべきか">hclwriteパッケージを利用すべきか</h2><p>究極的には生成したいTerraformコードがどのようなものであるかに依存しますが、あるTerraformコードを読み取って別のファイルを生成するだけであれば、hclwriteパッケージを用いてもそれほど難しくはありません。</p>
<p>今回の内容であれば、パース部分もHCLの構造を無視し、スクラッチで解析しても良さそうなレベルではあります。しかし、複数のリソースタイプが混ざったり、ある属性の条件でのみを対象としたいといった拡張はしばしばありえるので、こういったライブラリを用いてパースすると良いでしょう。</p>
<p>一方で生成側です。今回の用途だと、既存ファイルの更新ではなく新規生成です。しかも成果物の構造のシンプル。この場合は、<code>hclwrite</code> の仕様を学んでゴリッと出力するより、Go Templateなどお好きなてプレートエンジンで生成するほうが遥かにメンテナンスがしやすいいと思います。HCLのフォーマットだけは使っても良いかもしれませんが、全てをhclwriteで閉じて生成するのは、出力がこれだけであれば割に合わない気がしました。</p>
<p><code>hclwrite</code> ですが主な用途はすでに存在するTerraformのコードを破壊せず、一律でタグを付けたり属性を変えたりといった用途に向いているパッケージのようです。</p>
<h2 id="さいごに">さいごに</h2><p>hclwriteというパッケージを用いてあるTerraformリソースから、別のリソースを生成しました。新規生成については別のテンプレートエンジンを利用する方が良いかなと個人的には思います。</p>
<p>こういった開発フローは少し特殊で、通常はTerraformモジュール化などを試みると思いますが、モジュール化するにしてはリソース対象が少なく、ちょっと抽象度が弱いんだよな～といった場面では、コード生成案も考えてみても良いのではないでしょうか。</p>
]]></content>
    <summary type="html">Terraformファイルをコード生成するため、hclwriteというGoパッケージの使い方を調べました。</summary>
    <category term="IaC" scheme="https://future-architect.github.io/categories/IaC/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
    <category term="コード生成" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%BC%E3%83%89%E7%94%9F%E6%88%90/"/>
  </entry>
</feed>
