CI/CDへのInfracost統合:Terraformのクラウドコスト試算と可視化

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

課題:デプロイ後に発覚する想定外のクラウドコスト

半年前、私たちのチームはステージング環境のデータベーススループットを向上させるため、日常的なTerraformの更新をデプロイしました。自動テスト、静的解析(lint)、セキュリティスキャンはすべて問題なくパスしました。しかし3週間後、財務部門からAWSの請求額が想定外に4,200ドルも跳ね上がっていると指摘を受けました。あるエンジニアが、標準的なgp3ボリュームの代わりに、10,000 IOPSを指定したマルチAZのプロビジョンドIOPS SSD(io2)ストレージを3つの非本番クラスタに誤ってプロビジョニングしていたのです。

多くのFinOpsワークフローは、AWS Cost ExplorerやCloudHealthのような事後確認用のダッシュボードに依存しています。これらは月次の会計処理には役立ちますが、アラートが届くのはすでにお金が使われてしまった後です。terraform applyを実行した後に膨れ上がったインフラ費用を調査・対応することは、プラットフォームチームと開発者の間に余計な摩擦を生む原因になります。

シフトレフトの実践は、自動テストや脆弱性スキャンを大きく変革しました。クラウド支出に対しても同様のプロアクティブなアプローチを取り入れることで、コードがメインブランチにマージされる前に、高額なプロビジョニングミスを未然に防ぐことができます。

基本概念:Infracostがコスト意識のシフトレフトを実現する仕組み

InfracostはTerraformのHCLコードまたはJSON実行プランを解析し、宣言されたすべてのリソースをAWS、Azure、Google Cloudなどのベンダー価格データと照合します。コストを静的に見積もるため、実際のクラウドインフラに問い合わせることなく、迅速に価格の試算結果を取得できます。

主要なアーキテクチャコンポーネント

  • Cloud Pricing API: 300万件以上のクラウドSKUを追跡するマネージドサービス。プロバイダーが時間単位や階層別の料金プランを変更するたびに継続的に更新されます。
  • Diff Engine: ターゲットブランチとプルリクエスト間の月額コストの差分(デルタ)を計算し、リソースの追加・更新・削除ごとに内訳を表示します。
  • Usage Profiles: 500 GBの標準S3ストレージや2,500万回のLambdaリクエストなど、動的な使用量パラメータをinfracost-usage.ymlファイル経由で定義できます。
  • CI/CD Commenter: マークダウン形式のコスト内訳をGitHubのPull RequestやGitLabのMerge Requestに直接コメントとして投稿します。

実践:InfracostをGitHub Actionsに統合する

ローカル環境にCLIをインストールし、サンプルのリソース差分を確認した上で、自動化されたGitHub Actionsワークフローを設定してみましょう。

ステップ1:Infracost CLIのインストールと認証

まずは、ローカル開発中にコストの内訳を確認できるよう、作業端末にCLIバイナリをインストールします。

# Homebrewを使用したmacOSへのインストール
brew install infracost

# Linuxへのインストール
curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh

# 認証してAPIキーを取得
infracost auth login

ステップ2:ローカルでコスト内訳を生成する

EBSルートディスクがアタッチされたEC2インスタンスを定義する、シンプルなTerraform設定を見てみましょう。

# main.tf
provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "web_app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "m5.xlarge"

  root_block_device {
    volume_size = 100
    volume_type = "gp3"
  }
}

ディレクトリに対して直接コスト計算を実行します。

infracost breakdown --path .

InfracostがHCLファイルを解析し、月額コストのサマリーを出力します。

Name                                                  Monthly Qty  Unit         Monthly Cost

aws_instance.web_app
├─ Instance usage (Linux/UNIX, on-demand, m5.xlarge)          730  hours             $140.16
└─ root_block_device
   └─ Storage (general purpose SSD, gp3)                      100  GB                  $8.00

OVERALL TOTAL                                                                        $148.16

ステップ3:GitHub Actionsでプルリクエストのコスト差分を自動化する

コードレビューの段階でコストの悪化を検知することで、エンジニアリングチーム全体で支出の透明性を維持できます。APIトークンをINFRACOST_API_KEYという名前でリポジトリのSecretsに登録し、.github/workflows/infracost.ymlに以下のワークフローファイルを追加します。

name: Infracost Cost Estimation

on:
  pull_request:
    branches: [ main, master ]
    paths:
      - 'terraform/**'
      - '**.tf'

jobs:
  infracost:
    name: Infracost PR Cost Diff
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write

    steps:
      - name: Checkout Base Branch
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.ref }}

      - name: Setup Infracost
        uses: infracost/actions/setup@v3
        with:
          api-key: ${{ secrets.INFRACOST_API_KEY }}

      # ベースブランチからベースラインのコストを生成
      - name: Generate Infracost Baseline
        run: |
          infracost breakdown --path=terraform \
                              --format=json \
                              --out-file=/tmp/infracost-base.json

      - name: Checkout Current PR Branch
        uses: actions/checkout@v4

      # ベースラインに対する差分を生成
      - name: Generate Infracost Diff
        run: |
          infracost diff --path=terraform \
                         --format=json \
                         --compare-to=/tmp/infracost-base.json \
                         --out-file=/tmp/infracost-diff.json

      # コスト内訳をGitHub PRのコメントとして投稿
      - name: Post Infracost Comment
        run: |
          infracost comment github --path=/tmp/infracost-diff.json \
                                   --repo=$GITHUB_REPOSITORY \
                                   --github-token=${{ secrets.GITHUB_TOKEN }} \
                                   --pull-request=${{ github.event.pull_request.number }} \
                                   --behavior=update

ステップ4:FinOpsガードレールの構築

可視化も重要ですが、予期せぬ過剰なコスト増を防ぐには強制力のあるガードレールが必要です。生成されたJSON差分をjqで解析し、プルリクエストによる月額コストの増加が設定した上限(例:管理者の承認なしで月額200ドル以上の増加)を超えた場合にビルドを失敗させることができます。

# jqを使用して月額コストの差分を抽出
DIFF=$(jq -r '(.diffTotalMonthlyCost // 0)' /tmp/infracost-diff.json)
THRESHOLD=200

echo "予測される月額変更額: $$DIFF"

if (( $(echo "$DIFF > $THRESHOLD" | bc -l) )); then
  echo "エラー: コスト増加額 $$DIFF が上限値 $$THRESHOLD を超えています。FinOpsの承認が必要です。"
  exit 1
fi

本番運用6ヶ月で得られた教訓

40以上のマイクロサービスでInfracostを運用する中で、プラットフォームエンジニアにとって実践的ないくつかの教訓が得られました。

  1. 使用量プロファイル(Usage Profiles)を早期に定義する: サーバーレスコンポーネント(AWS Lambda、DynamoDBオンデマンド、S3 Glacierなど)は、infracost-usage.ymlで想定される呼び出し回数やストレージ容量を指定しない限り、デフォルトで0.00ドルとして計算されます。
  2. モノレポ設定を一元化する: リポジトリが環境ごとに異なるパス(environments/stagingやenvironments/prodなど)に分かれている場合は、infracost.ymlでマルチプロジェクト設定を宣言し、すべての対象を同時に評価できるようにします。
  3. フィードバックが自発的な修正を促す: プルリクエスト内でコンピュート層をc5.2xlargeからc5.4xlargeに変更した際に月額280ドルの増加が表示されると、エンジニアはレビューを依頼する前に自発的にワークロードの適正サイズ化(ライトサイジング)を行うようになります。

まとめ

GitHubのプルリクエストに直接コストの可視化を導入することで、FinOpsは月末の受動的な監査から、自動化されたエンジニアリングのセーフガードへと進化します。Infracostを既存のCI/CDパイプラインと組み合わせて、クラウド予算の透明性を保ち、管理しやすく予測可能なインフラ運用を実現しましょう。

Share: