🎃

AWSのコストをInfracost+Github Actionsでいい感じに把握する。

2022/02/04に公開3

直近のミッションとしてterraformやAWSの開発チームへの民主化(権限移譲)などを進めていくにあたり、

  • AWSのコストをterraformでのリソース作成時等に視覚化して開発者にもコスト感を共有したい
  • CI時にコスト算出を実施して、リソース作成時の試算等の手間を減らしたい
  • CI(terraform plan)時にコスト算出した結果をPRなどにコメントを残したい

というモチベーションがあり、当初自作ツールなどで対応しようと考えていましたが、 Infracostというツールでいい感じに達成できそうだったので試してみました。
https://github.com/infracost

構築手順

セットアップ&登録

まずは公式の手順に従って、infracostのバイナリを取得して、
infracostに登録します。同時にinfracostのAPIキーが発行されるので、
どこかにメモしておきます。

$ infracost register
Please enter your name and email address to get an API key.
See our FAQ (https://www.infracost.io/docs/faq) for more details.
Name: hogehoge
Email: xxxx@xxx.xxx

Thank you hogehoge!
Your API key is: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Github ActionsにWorkflow追加

APIキーの設定

先ほどの手順で取得したAPIキーをGithub Actionsから参照できるように、Actions SecretsにINFRACOST_API_KEYとして登録します。

Github Actions ワークフローの追加

ワークフローについては、公式に設定例があるので、そちらを参考に設定を進めました。
ただ、私の環境下ですとterraform planの実行と並行してinfracostのJOBを実行する形で当初設定していたのですが、tfstateのロックが競合してしまう為かうまく動作しなかったため、
一旦terraform planのワークフロー内にアドオンする形にしました。
最終的にできた設定としては下記のような形になります。
{TERRAFORM_DIRECTORY}{ACCOUNTID}は適宜読み替えていただければと思います。

terraform-plan.yml
name: terraform plan job

on:
  pull_request:
    branches:
      - main
    paths:
      - "{TERRAFORM_DIRECTORY}"

env:
  WORKING-DIRECTORY: {TERRAFORM_DIRECTORY}
  AWS_ROLE_ARN: arn:aws:iam::{ACCOUNTID}:role/{RoleName}

jobs:
  terraform:
    name: terraform plan
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      id-token: write
      contents: read

    defaults:
      run:
        shell: bash

    steps:
    - run: sleep 5

    - name: Checkout
      uses: actions/checkout@v2

    - name: Setup AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
          role-to-assume: ${{ env.AWS_ROLE_ARN }}
          aws-region: ap-northeast-1
          role-duration-seconds: 900
          role-session-name: GitHubActionsTerraformCICD

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: 1.0.10

    - name: Terraform Format
      id: fmt
      working-directory: ${{ env.WORKING-DIRECTORY }}
      run: terraform fmt -recursive -check=true

    - name: Terraform Initialize
      id: init
      working-directory: ${{ env.WORKING-DIRECTORY }}
      run: terraform init

    - name: Terraform Validate
      id: validate
      working-directory: ${{ env.WORKING-DIRECTORY }}
      run: terraform validate -no-color

    - name: Terraform Plan
      id: plan
      working-directory: ${{ env.WORKING-DIRECTORY }}
      run: terraform plan -no-color
    
    - uses: actions/github-script@0.9.0
      if: github.event_name == 'pull_request'
      env:
        PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
        script: |
          const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
          #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
          #### Terraform Validation 🤖\`${{ steps.validate.outputs.stdout }}\`
          #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
          
          <details><summary>Show Plan</summary>
          
          \`\`\`\n
          ${process.env.PLAN}
          \`\`\`
          
          </details>
          
          *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;
            
          github.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: output
          })

    - name: Terraform plan
      working-directory: ${{ env.WORKING-DIRECTORY }}
      run: terraform plan -out tfplan.binary
      

    - name: Terraform show
      working-directory: ${{ env.WORKING-DIRECTORY }}
      run: terraform show -json tfplan.binary > plan.json

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

    - name: Generate Infracost JSON
      working-directory: ${{ env.WORKING-DIRECTORY }}
      run: infracost breakdown --path plan.json --format json --out-file /tmp/infracost.json

    - name: Post Infracost comment
      uses: infracost/actions/comment@v1
      with:
         path: /tmp/infracost.json
         behavior: update 

実行結果

実行の結果については下記の様にPRのコメントに記載されます。
Previousは現在の料金、Newには新規リソースをApplyした際の料金が記載されるようです。

所感

公式に従ってつらつらとインストールしてみましたが、概ね問題なく動くようでした。
ただし、

  • AutoScalingやVPCのトラフィック量などの変動費についてはおそらくわからない?(あくまでterraformのplan結果をパースしているだけなので固定費のみ?)
  • terraform planを2回ほどCI時に回すことになるため、やや処理が冗長

というあたりに課題があったりするかなという感じで、
このあたりはもう少し使い込んでみて深堀していきたいと思います。

Discussion

AliAli

日本語が話せなくてごめんなさい、google_translateを使っています...

自動スケーリングおよびその他の使用量ベースのコストについては、https://infracost.io/usage-fileを使用できます。

これには、AWSからデータをフェッチするオプションもあります(たとえば、aws_autoscaling_groupおよびaws_eks_node_groupのインスタンスの数)

suggysuggy

情報ありがとうございます。AutoScalingのコスト算出は欲しかった機能でした。
頂いた情報を基に試してみますね。

Ali.
Thank you very much for the information.
The Autoscaling cost estimation was a feature I was hoping for.
I will try to use the information you gave me.

AliAli

@suggy great! Please let me know if you have any feedback for it - we're going to focus on that feature