CURをduckdbでクエリしてevidenceでチャートにする
BigQuery OmniでS3出力されたCURへクエリを投げたい
↑こちらの記事の続きです。
AWSコスト可視化のための環境は2日で作れるようになりました。
が、BigQuery + Athena + Lookerという環境なので、開くたびにクエリが実行され課金対象になってしまいます。
コスト削減のためにコストがかかるのも本末転倒感があるな、と思っていたところ、ちょうどduckdbを弄りたかったのでちょうどいいネタになると思い作ってみました。
CURを取得する
前回の記事では、Cost Usage Report(以下、CUR)をParquet形式で出力しておきました。
これにduckdbでクエリを投げます。
CREATE OR REPLACE TABLE cur AS
SELECT line_item_usage_start_date,
line_item_product_code,
SUM(line_item_unblended_cost) AS cost
FROM "s3://${account_id}-cur/cost-report/${account_id}-cost-report/${account_id}-cost-report/year=${year}/month=${month}/*.parquet"
GROUP BY 1, 2;
S3のパスなどはterraformでの作成時に指定した通りです。
(この辺り、tfstateから取ってこられると、もっとスマートかもしれません)
これで、duckdbファイル内のcurテーブルに時間単位・AWSプロダクト単位のコストが集計されました。
duckdbなので、GROUP BYももっとスマートに書けるのですが手抜きしています。
evidenceでチャート作成
evidenceをセットアップします。
https://github.com/evidence-dev/template#get-started-using-the-cli こちらの通り実行します。
npx degit evidence-dev/template my-project
cd my-project
npm install
npm run sources
npm run dev
sampleのチャートが表示されることを確認して、ターミナルからCtrl + Cで終了させます。
duckdbへのクエリ結果をチャートにしたいので、新規のsourceを追加します。
mkdir -p sources/cur_${account_id}
cp cur.duckdb sources/cur_${account_id}/
echo 'select * from cur' > sources/cur_${account_id}/cur.sql
cp sources/needful_things/connection.yaml sources/cur_${account_id}/
sources/cur_${account_id}/connection.yamlをduckdb用に書き換えます。
type: duckdb
name: cur_${account_id}
options:
filename: cur.duckdb
sourceの追加・修正の都度、npm run sourcesを実行します。
pages/index.mdを書き換えて、duckdbへのクエリ結果を出力するようにします。
markdownで書くSQLからは、sourcesファイル中の*.sqlがテーブル扱いになるようです。
(cur.sqlを置かないと、「そんなテーブルないよ!」ってエラーが出ます)
---
title: CUR
---
<BarChart
data={cur_${account_id}}
title="AWS Daily Costs"
x=date
y=cost
series=product
/>
```sql cur_${account_id}
SELECT date_trunc('day', line_item_usage_start_date) AS date,
line_item_product_code AS product,
SUM(cost) AS cost
FROM cur_${account_id}.cur
GROUP BY date, product;
```
チャートになりました。
時刻単位で集計したらBarが細くなりすぎて見づらいので日毎の集計にしています。
GitHub Pagesにデプロイ
GHECでprivateなGitHub Pagesが利用できるプランを契約しているので、社内公開してみます。
リポジトリのSettingsからGitHub Pagesを有効化し、デプロイ用のGitHub Actions workflowを作成します。
---
name: Deploy to GitHub Pages
on:
schedule:
- cron: "0 0 * * *" # 09:00 毎日 JST
workflow_dispatch:
jobs:
fetch:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- name: Checkout
timeout-minutes: 3
uses: actions/checkout@v4
- name: Set up AWS credentials
timeout-minutes: 3
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.OIDC_ARN }} # OIDC用のarnを指定
aws-region: us-west-1
- uses: aquaproj/aqua-installer@v3.1.0
timeout-minutes: 3
with:
aqua_version: v2.40.0
- name: Update duckdb
run: |
./update_duckdb.bash
- name: Upload cur.duckdb
uses: actions/upload-artifact@v4
with:
name: cur_${account_id}
path: sources/cur_${account_id}/cur.duckdb
retention-days: 1
deploy:
needs: fetch
runs-on: ubuntu-latest
permissions:
pages: write
contents: read
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout
timeout-minutes: 3
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: 'package.json'
- uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Download cur.duckdb
uses: actions/download-artifact@v4
with:
name: cur_${account_id}
path: sources/cur_${account_id}
- name: Build
run: |
npm i
npm run sources
npm run build
- name: Upload Artifacts
uses: actions/upload-pages-artifact@v3
with:
path: 'build'
- name: Deploy
id: deployment
uses: actions/deploy-pages@v4
update_duckdb.bashの中身は↓これです。
原因は調査中なのですが、duckdb経由でのS3へのアクセスで400エラーが出るため、parquetファイルをダウンロードして読み込むようにしました。
#!/usr/bin/env bash
set -euo pipefail
set -xv
account_id=$(aws sts get-caller-identity --query Account --output text --no-cli-pager)
cd "sources/cur_${account_id}/"
rm -f cur.duckdb
aws s3 cp s3://${account_id}-cur/cost-report/${account_id}-cost-report/${account_id}-cost-report/ . --recursive
duckdb cur.duckdb <<SQL
CREATE OR REPLACE TABLE cur AS
SELECT line_item_usage_start_date,
line_item_product_code,
SUM(line_item_unblended_cost) AS cost
FROM "./**/*.parquet"
GROUP BY 1, 2;
SQL
また、上記は1アカウントでの結果出力ですが、複数アカウントでの結果を集計して1ページに出力したくてupload/download-artifact actionを使っています。
まとめ
duckdbもevidenceもいいぞ!
ところで、pnpmを最近使っているのですが、evidenceテンプレートからpnpm i && pnpm sources && pnpm dev
するとviteコマンドがない、とエラーが出ます。
そのため、npmにもどしたりしました...。frontend何もわからん。
Discussion