Quantcast
Channel: kakakakakku blog
Viewing all 903 articles
Browse latest View live

Generative AI Test (2024 #2) を受験してみた

$
0
0

2024年12月7日に「Generative AI Test (2024 #2)」を受験して合格した👌試験自体は IBT (Internet Based Testing) だけど結果はすぐに出ず,2024年12月20日に発表された.

Generative AI Test は「G 検定」「E 資格」を運営している JDLA によって提供されていて,生成 AI の基礎知識を確認できる.ウェブサイトに 生成 AI に特化した知識や活用リテラシーの確認の為のミニテストです。と書いてある通り,資格試験というよりは「ミニテスト」と表現するのが適切だと思う👌試験時間はたった20分間だし,受験費用も2000円だし,非常に受験しやすいと思う.

今回は「ミニテストの紹介を目的として」出題内容には触れず簡単に振り返っておこうと思う.

www.jdla.org

出題範囲

出題範囲はウェブサイトに載っている📝

理解しておくべき用語が網羅されているため不安な用語があったら調べておくと良いと思う.

  • 生成 AI の技術
    • 特徴
    • 動向
  • 生成 AI の利活用
    • 特徴
    • 動向
  • 生成 AI のリスク
    • 特徴
    • 動向

Generative AI Testから引用

ちなみに Generative AI Testには専用の資格対策本はなく,日頃からウェブサイトや書籍を読んで基礎知識を持っていれば OK👌というメッセージのように感じた.

出題形式

ミニテストの出題形式はウェブサイト載っていて「計20問」出題される✏️

  • 択一式 / 多肢選択式: 19問
  • 記述式: 1問

以下に例題として9問公開されているため確認しておくと良いと思う.

www.jdla.org

なお,試験時間は「20分間」で,正直時間はギリギリだと思う.僕個人としては,選択式を1問30秒以内でサクサク進めて,記述問題もサッと回答して,残った時間でもう一度見直しをするような流れだった.

試験対策

普段からソフトウェアエンジニアとして生成 AI を活用していて,また2024年10月に「生成 AI パスポート試験(2024年 第3回)」を受験して合格しているという背景もあるため,今回の Generative AI Test に関してはほとんど対策をしなかった.

kakakakakku.hatenablog.com

以下の zero to oneというウェブサイトに出題範囲の用語解説が載っているため,ミニテスト当日の朝にいくつかの用語解説を確認して,そのまま受験をしてしまった💡結果的には特に問題なかった.

zero2one.jp

まとめ

Generative AI Test は資格試験よりもカジュアルに受験できるミニテストという感じで良かった❗️

もし興味あったら,2025年6月7日の次回開催 Generative AI Test (2025 #1)に申し込んでみると良いんじゃないでしょうか😀


AWS CloudFormation の DeletionPolicy: RetainExceptOnCreate を試す

$
0
0

AWS CloudFormation でリソースをデプロイするときに「意図的に」リソースを残すために DeletionPolicy: Retainを設定することがある👌しかし AWS CloudFormation スタックを新しく作るときに AWS CloudFormation テンプレートが間違っていてエラーになってしまうとログを見ながらトラブルシューティングが必要になる❗️(よくある)

docs.aws.amazon.com

そのときに AWS CloudFormation スタックのデフォルトの挙動は「ロールバック」になるため,もし DeletionPolicy: Retainを設定したリソースがあるとそのまま残ってしまう.エラー原因を特定して「さぁ!再デプロイするぞー💪」と思っても,その前にマネジメントコンソールで削除する必要があって,ああ面倒だ〜😇という気持ちになる.

ロールバックを無効化する

ちなみに2021年にリリースされた設定を使うとロールバックを無効化できてトラブルシューティングがしやすくなる👌リリース記事に shortening development cycles(開発サイクルを短縮する)と書いてあってまさに❗️という感じ.

aws.amazon.com

docs.aws.amazon.com

DeletionPolicy: RetainExceptOnCreateを設定する

別の選択肢として,2023年にリリースされた DeletionPolicy: RetainExceptOnCreateを設定することもできる👌簡単に言うとリソースを作るときにエラーになった場合は DeletionPolicy: Deleteの挙動になって,一度リソースを作ったら DeletionPolicy: Retainの挙動をするという「それは助かる〜」と思う挙動を実現できる.リリース記事のタイトルに to accelerate dev-test cycle(開発とテストのサイクルを加速するため)と書いてあってこれまたまさに❗️

aws.amazon.com

実は今まで RetainExceptOnCreateを試したことがなく,最近使う機会があったのでメモを残しておこうと思う📝

1. DeletionPolicy: Retainの場合

今回は AWS SAM (AWS CloudFormation) で Amazon CloudWatch Logs Group と AWS Lambda 関数をデプロイする.そして Amazon CloudWatch Logs Group に DeletionPolicy: Retainを設定しつつ,意図的に AWS Lambda 関数に設定する IAM Role を誤った ARN にしておく😇

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:Logs:Type: AWS::Logs::LogGroup
    Properties:LogGroupName: sandbox-retain-except-on-create
    DeletionPolicy: Retain
  Function:Type: AWS::Serverless::Function
    Properties:FunctionName: sandbox-retain-except-on-create
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.12
      Architectures:- x86_64
      Role: arn:aws:iam::000000000000:role/xxxxx
      LoggingConfig:LogGroup:!Ref Logs

デプロイをするとエラーになって AWS CloudFormation スタックのステータスは ROLLBACK_COMPLETEになる.

Resource handler returned message: "The role defined for the function cannot be assumed by Lambda.

しかし DeletionPolicy: Retainを設定しているため Amazon CloudWatch Logs Group は残ってしまう.

2. DeletionPolicy: RetainExceptOnCreateの場合

今度は Amazon CloudWatch Logs Group に DeletionPolicy: RetainExceptOnCreateを設定する.

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:Logs:Type: AWS::Logs::LogGroup
    Properties:LogGroupName: sandbox-retain-except-on-create
    DeletionPolicy: RetainExceptOnCreate
  Function:Type: AWS::Serverless::Function
    Properties:FunctionName: sandbox-retain-except-on-create
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.12
      Architectures:- x86_64
      Role: arn:aws:iam::000000000000:role/xxxxx
      LoggingConfig:LogGroup:!Ref Logs

デプロイをするとエラーになって AWS CloudFormation スタックのステータスは ROLLBACK_COMPLETEになる.

しかし Amazon CloudWatch Logs Group は消えていた❗️

そして今度は AWS Lambda 関数に設定する IAM Role を正しく修正して,一度デプロイを成功させてから AWS CloudFormation スタックを削除する🗑️

するとリソースを作った後の操作になるため Retainになって Amazon CloudWatch Logs Group は残っていた👏

2024年のプルリクエストを振り返る

$
0
0

2016年から毎年送ったプルリクエストを振り返る記事を書いている📅

2024年は「計10件」だった❗️2025年も機会があれば積極的にプルリクエストを送っていくぞー \( 'ω')/

プルリクエストを振り返るための検索

プルリクエストを振り返るために GitHub の検索条件を使う.今回は「2024年」に限定するため created:2024とする.

is:pr is:public author:kakakakakku -user:kakakakakku created:2024 sort:created-asc

2024/01

hasura/learn-graphql

仕事で Hasura を使うことになって2023年末から勉強していた📝 PostgreSQL Tutorialを試しているときに SQL の誤りを発見して修正した✅

github.com

aws-samples/amazon-s3-multipart-upload-transfer-acceleration

Amazon S3 で署名付き URL を使ったファイルアップロードの参考実装として Uploading large objects to Amazon S3 using multipart upload feature and transfer accelerationを試しているときに AWS Lambda 関数がタイムアウトになる場合があって修正した✅️ タイムアウト値は AWS CDK のコンテキスト値を使うと良いのでは〜?というアドバイスももらえた.

github.com

Uploading large objects to Amazon S3 using multipart upload feature and transfer acceleration に関しては以下の記事にまとめてある.

kakakakakku.hatenablog.com

2024/04

aws/amazon-sagemaker-examples

Amazon SageMaker の地理空間 (Geospatial) 機能を試すときに Jupyter Notebook の CI Badge が古くてエラーになっていたのを修正した✅ プルリクエストは見てもらえてなく Open のまま...

github.com

How to use SageMaker Processing with geospatial image に関しては以下の記事にまとめてある.

kakakakakku.hatenablog.com

2024/07

localstack/docs

LocalStack で Amazon DynamoDB TTL を試しているときにドキュメントに載っているエンドポイントが間違っていて(動くには動く)修正した✅️

github.com

LocalStack の Amazon DynamoDB TTL に関しては以下の記事にまとめてある.

kakakakakku.hatenablog.com

2024/11

localstack/localstack

LocalStack のコードを毎日コツコツ読むという活動を11月から始めた.テストコードのカバレッジを上げることに貢献できそうだと思って AWS Systems Manager Parameter Store の未実装のテストコード (GetParameterHistory API と GetParameter API)を追加した✅️ プルリクエストはまだ見てもらえてなく Open のままだけど,pytest で通常の値比較のテスト以外に実際の AWS 環境で取得したスナップショットと比較するテストもあって,流れを体験できたのは良かった👌

github.com

github.com

mingrammer/diagrams

比較的シンプルなアーキテクチャ図を作るときに Diagramsを使っていて,Amazon DynamoDB Streams のアイコンが登録されてなく追加した✅️ ちなみに Diagrams に登録されているデータベース関連のサービスアイコンは旧世代のデザインのままになっていて,このときは現行踏襲で旧世代のアイコンを追加した.

github.com

2024/12

localstack/localstack

LocalStack に Amazon EventBridge Scheduler の未実装のテストコード(TagResource API と UntagResource API)を追加した✅️ 新規で追加するコードは基本的にスナップショットテストも含めるべきというアドバイスももらいつつ merge してもらえた❗️

github.com

mingrammer/diagrams

Amazon S3 Object Lambda のチュートリアルを試しているときに Diagrams でアーキテクチャ図を書こうかな〜と思ったら Amazon S3 Access Points と Amazon S3 Object Lambda Access Points のアイコンが登録されてなく追加した✅️

github.com

Amazon S3 Object Lambda に関しては以下の記事にまとめてある.

kakakakakku.hatenablog.com

localstack/docs

LocalStack で Amazon OpenSearch Service を試しているときにドキュメントに載っているコマンドだとうまくドメインに接続できず修正した✅️ シェルのヒストリ機能 !1が影響していて気付くまでに結構ハマった.

github.com

読んだ本を振り返る(2024年9-12月)

$
0
0

2024年2月から「毎日10分間読書」という習慣化を始めたことをキッカケに毎日コツコツと本を読めるようになった1年だった📕2024年9月から2024年12月までに読んだ本とその感想(書評記事 or X ポスト)をまとめておこうと思う📝2024年8月までの振り返りは以下の記事にまとめてある.

kakakakakku.hatenablog.com

4ヶ月間で「計15冊」読めた❗️2024年全体だと「計41冊」読めた.今まではまとまった時間ができたら読もうと思って結果積読になってしまったり,ブログに書評記事をしっかりまとめようと思いすぎて進まなくなってしまっていて,1年間でここまで多く読めたのははじめてだと思う.「毎日10分間読書」の習慣化を始めて良かったな〜と本当に思う \( 'ω')/

📕 2024年9月: 4冊😃

アドレナリンジャンキー

アジャイルチームによる目標づくりガイドブック

実践 Next.js

マッチ箱の脳

📕 2024年10月: 6冊😃

ハイパーモダン Python

ソフトウェアファースト 第2版

勉強が面白くなる瞬間

おとなもこどもも知りたい 生成 AI の教室

TRANSFORMED

税金でこれ以上損をしない方法

📕 2024年11月: 3冊😃

LangChain と LangGraph による RAG・AI エージェント[実践]入門

kakakakakku.hatenablog.com

Joel on Software

番狂わせの起業法

📕 2024年12月: 2冊😃

マスタリングAPIアーキテクチャ

DuckDB in Action

習慣化 振り返り(2024年7-12月)

$
0
0

2024年から Habitifyを使って個人的な習慣化管理を楽しんでいる❗️

Habitify 最高〜 \( 'ω')/ あっという間に Habitify 歴1年になってしまった🕐️

www.habitify.me

2024年後半(7-12月)の習慣化を振り返ろうと思う \( 'ω')/ 過去の振り返りは以下にまとめてある📝

kakakakakku.hatenablog.com

朝活🌅

2023年2月から2年間「朝活」を続けていて,Habitify で活動内容を毎日記録している.今までの朝活に関しては以下の記事にまとめてある☕

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

2024年後半(7-12月)で朝活を「182日」継続できたー👏 1年間だと「358日」継続できた.

ちなみに朝活を skip して OK という個人的な条件として「飲み会の翌朝で睡眠を優先したい日」「体調不良の日」の2つを決めていて,半年間で2日は skip した(どちらも飲み会の翌朝).

朝活メトリクス

朝活で取り組んだこと

Habitify API を使って Habitify メモを集計してみた.182日間コツコツとインプットできた❗️とにかく朝活時間を確保してなかったらここまでインプットできてないな〜と思っていて,2025年も続けていく.活動内容を曖昧に書いてしまっている日もあってそこは見直したいな〜と思っている.

  24 LocalStack
  15 Zenn Book (Serverless Pattern) 執筆
  11 LangChainとLangGraphによる RAG・AI エージェント[実践]入門
  10 ラジオ体操
  10 実践 Next.js
  10 Docusaurus
   8 GitHub CI/CD 実践ガイド
   7 moto
   6 エンジニアリングマネジャー入門
   6 ハイパーモダン Python
   6 生成 AI パスポート公式テキスト 第2版
   6 Lambda Powertools
   5 Testcontainers
   5 GitHub: Automatically generated release notes
   5 DuckDB ドキュメントリーディング
   4 何もしない習慣
   4 uv
   4 LocalStack x OpenSearch 検証
   4 Dify
   4 Bytebase
   4 Auth0 Terraform Provider
   3 Service Quotas
   3 Microsoft Learn GitHub Copilot の基礎
   3 Handling Rewrites and Redirects using Edge Functions
   2 EventBridge Scheduler Group
   2 Cognito トリガー検証
   2 CDK: crossRegionReferences 検証
   2 AWS Step Functions
   1 actionlint
   1 Grand
   1 DuckDB x LocalStack S3 統合
   1 DuckDB Tutorial For Beginners
   1 CDK x API Gateway x Custom Domain
   1 AWS What's New
   1 AWS CloudFormation Git sync

10分間読書📕

2024年2月12日から「10分間読書」という習慣化に取り組んでいる.毎日コツコツ読めるようになって本当に自分に合っている習慣化になった❗️

2024年前半(7-12月)で10分間読書を「184日」継続できたー👏 1年間だと「324日」継続できた.

10分間読書メトリクス

10分間読書で読んだ本

Habitify API を使って Habitify メモを集計してみた.

  18 マスタリング API アーキテクチャ
  17 DuckDB in Action
  15 ソフトウェアファースト 第2版
  14 開発生産性の教科書
  13 Joel on Software
  11 TRANSFORMED
  10 番狂わせの起業法
   9 農業のしくみとビジネスがこれ1冊でしっかりわかる教科書
   8 アジャイルチームによる目標づくりガイドブック
   7 税金でこれ以上損をしない方法
   7 ランサムウエアから会社を守る
   7 勉強が面白くなる瞬間
   6 データ農業が日本を救う
   6 アドレナリンジャンキー
   6 マッチ箱の脳
   5 農業と環境調査のためのリモートセンシング・GIS・GPS活用ガイド
   5 図解よくわかるスマート農業
   5 AWS クラウドネイティブデザインパターン
   4 スマート農業のきほん
   4 図解でよくわかる 病害虫のきほん
   4 コード×AI
   3 朝イチの「ひとり時間」が人生を変える

読書の振り返りは以下にまとめてある📝

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

OSS コードを読む🧑‍💻

日常的にコードを読むことを習慣化したいなと思って,2024年11月1日から「OSS コードを読む」という習慣化に取り組むことにした.普段使っているライブラリの実装を知ることができて,貢献できるところはプルリクエストを送ることもできて,毎日楽しめる新しい習慣化になった👌

2024年後半(11-12月)で OSS コードを読むのを「61日」継続できたー👏

コードを読んだリポジトリ

  49 LocalStack
   9 Powertools for AWS Lambda (Python)
   3 Diagrams

プルリクエストの振り返りは以下にまとめてある📝

kakakakakku.hatenablog.com

その他

他にも習慣化を管理している💪

  • ビタミンを摂る(毎日)
  • 1週間を振り返る(毎週)

習慣化に悩む人におすすめの一冊

僕自身は興味のあることを習慣化するのが得意だとは思うけど,もし習慣化に悩む人がいたら「小さな習慣」をおすすめしたい📕

kakakakakku.hatenablog.com

まとめ

2025年も毎日 Habitifyを使って習慣化を楽しむぞー❗️

関連記事

Habitify API に関しては以下にまとめてある.

kakakakakku.hatenablog.com

Zenn Book で「LocalStack 実践入門 | AWS サーバレスパターン開発ワークショップ」を公開しました

$
0
0

あけましておめでとうございます🎍2025年1月1日に Zenn Book で完全無料の学習コンテンツ「LocalStack 実践入門 | AWS サーバレスパターン開発ワークショップ」を公開しましたー🎉

AWS エミュレーターの LocalStackに実践的に入門しつつ「AWS サーバレスパターン」を体験するワークショップです❗️

zenn.dev

概要 🚀

2024年8月に「LocalStack 実践入門 | AWS アプリケーション開発ワークショップ」を公開して,LocalStack が便利だぞ〜👏ということはある程度伝えられたかなと思います.と同時にもう一歩技術的に踏み込んだワークショップがあったら良いな〜とも思っていて,今回「AWS サーバレスパターン」をテーマにしたワークショップの開発を企画しました.内容的には「LocalStack 実践入門 | AWS アプリケーション開発ワークショップ」の続編です.

zenn.dev

kakakakakku.hatenablog.com

テーマとして選んだ「AWS サーバレスパターン」はウェブサイトとして公開されていて,"こういう仕組みを構築したかったらまずはこういうアーキテクチャから検討を始めると良いぞ〜"的なパターン集として再利用性が高く,個人的によく見るコンテンツの一つです.そして LocalStack Community Edition で試せるものも多く,「AWS サーバレスパターン」をテーマに独自コンテンツを作ったらおもしろいんじゃないかな〜と考えて企画しました.

aws.amazon.com

読者層 🎃

このワークショップは「LocalStack 未経験者」はもちろん「AWS 初学者」「AWS サーバレスアーキテクチャ未経験者」にもおすすめです❗️LocalStack を使えば AWS の課金を気にせず試せるため,特に「試しながら学ぶ」のに最適だと思います👌

ワークショップ構成 🧪

ワークショップは Chapter.1 から Chapter.8 まであります.Chapter.3 から Chapter.6 までは同じストーリーの延長になっていて,Chapter.7 と Chapter.8 は内容的に独立しています.そして Chapter.9 は「応援購入」のための付録です.ワークショップに関連する小ネタを紹介しています.次のワークショップを企画するモチベーションにも繋がりますので,よろしければぜひ❗️

  • Chapter.1: サーバレスパターンから学ぼう
  • Chapter.2: ワークショップ環境をセットアップしよう
  • Chapter.3: 画像処理をしよう
  • Chapter.4: エラーをモニタリングしよう
  • Chapter.5: 署名付き URL でファイルをアップロードしよう
  • Chapter.6: ワークフローを作ろう
  • Chapter.7: データの変更をトリガーしよう
  • Chapter.8: 音声からテキストに文字起こしをしよう
  • Chapter.9: 付録(応援購入)

アーキテクチャ図 🎨

LocalStack 上にデプロイするサーバレスアプリケーションのアーキテクチャ図を載せておきます.登場する AWS サービス(順不同)としてはこのあたりです👌

  • AWS CloudFormation
  • AWS SAM
  • Amazon S3
  • AWS Lambda
  • Amazon CloudWatch
  • Amazon CloudWatch Logs
  • Amazon SNS
  • Amazon API Gateway
  • AWS Step Functions
  • Amazon DynamoDB
  • Amazon DynamoDB Streams
  • Amazon Transcribe

Chapter.3

Chapter.4

Chapter.5

Chapter.6

Chapter.7

Chapter.8

まとめ 🐸

気になるな〜と思ってもらえたら「LocalStack 実践入門 | AWS サーバレスパターン開発ワークショップ」をお試しくださいませ❗️

zenn.dev

ポスト 🦜

カックマイクラ実況 YouTube 振り返り(2024年)

$
0
0

2023年7月1日に YouTube チャンネル「カックマイクラ実況」を開設して1年半たった❗️簡単に振り返ろうと思う.

www.youtube.com

ちなみに今までの振り返りは以下の記事にまとめてある📝

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

Stats (~2024/12/31) 👾

  • 動画: 105本(2024年7月から+34本)
  • チャンネル登録者数: 89人(2024年7月から+28人)
  • 視聴回数: 35,841回(2024年7月から+13,535回)

YouTube ショート 👾

引き続き YouTube ショートを中心に作っている❗️YouTube に使える自由時間が減っているという背景もあるけど,やはり長めの動画を作ってもほとんど見てもらえないということを考えると,企画・収録・編集のコストが高すぎるため,気軽に作れる YouTube ショートが今の自分には合っているな〜と思う \( 'ω')/

YouTube ショートだと,ショートフィード経由の新規流入があるのが嬉しいけど,逆に言うと YouTube のアルゴリズムに左右されてしまうのは気になる.公開タイミングによっては全然視聴されない YouTube ショートもあって残念な気持ちになることもあった.

YouTube ショートの視聴回数(2024年7月〜2024年12月)は500回ぐらいがベースラインになっていた👌

AI 建築シリーズ 👾

YouTube チャンネルのメイン企画として「AI 建築シリーズ 」を続けている.AI 建築の仕組みは前回の振り返り記事に載せている 「生成 AI + Raspberry Jam Mod (raspberryjammod)」という構成で変わらずだけど,プロンプトを調整しながら試行錯誤して,毎回イイ感じのコードを生成できている👌

AI 建築のテーマとしては身近にあるもの以外に「たまごっち」のドット絵を建築するシリーズも作った.個人的には「迷路」「虹色トンネル」がお気に入り❗️

www.youtube.comwww.youtube.comwww.youtube.com

Mod 紹介シリーズ 👾

他の企画として「Mod 紹介シリーズ」も始めた😀

CurseForge で良さそうな Mod を探して,試して YouTube ショートにまとめている.Mod をセットアップするにはある程度技術的な勘所が必要だけど,特にハマるところがないというのは僕自身の強みかな〜とは思う.

www.curseforge.com

個人的には「Carry On」「Revamped Phantoms」がお気に入り❗️

www.youtube.com

www.youtube.com

Google AI Studio: Stream Realtime の衝撃 👾

2024年12月に発表された Google AI Studio の Stream Realtime を使って AI と話しながらマインクラフトをプレイしてみた❗️これは本当にスゴくて未来がある.こういう AI 関連のホットな話題もマイクラ実況に融合して追いかけていきたいと思う.本当に未来のある YouTube ショートなので是非見てもらえればと👌

www.youtube.com

今後 👾

今後も引き続き趣味として YouTube 活動は続けていこうと思う❗️

また2025年は「カックマイクラ実況ブログ」を作る予定.自分自身マイクラ関連の技術情報を検索することも多く,ブログから YouTube チャンネルに流入してもらえる可能性もあるかな〜と考えたのと,動画は見ないけどブログなら見るという人もいると考えた.2025年は動画を毎週更新するというノルマは設定せず,ブログも含めてコツコツとアウトプットを楽しもうと思う.

チャンネル登録をしてもらえると本当に嬉しいのでもし良かったらポチッとお願いしまーす🙏

www.youtube.com

AWS CDK で Cost and Usage Reports 2.0 (CUR 2.0) エクスポートを設定する

$
0
0

AWS CDK で AWS Data ExportsCost and Usage Reports 2.0 (CUR 2.0)エクスポートを設定する機会があった💰️

L1 コンストラクトの aws_bcmdataexports.CfnExportを設定するときに AWS CloudFormation のドキュメントも確認しながら試行錯誤が必要だった.サンプルコードとして載せておこうと思う📝 ちなみに最初に見たときにモジュール名の bcmdataexportsって何?と思うかもしれないけど bcmAWS Billing And Cost Managementのことだと覚えておけば OK👌

docs.aws.amazon.com

👾 lib/cur.ts

import{
    RemovalPolicy,
    Stack,
    StackProps,
    aws_bcmdataexports,
    aws_iam,
    aws_s3,
}from'aws-cdk-lib'import{ Construct }from'constructs'exportclass CurStack extends Stack {constructor(scope:Construct, id:string, props?:StackProps) {super(scope, id, props)

        const bucket = new aws_s3.Bucket(this, 'CurBucket', {bucketName: 'xxxxx',
            removalPolicy: RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE,
        })

        bucket.addToResourcePolicy(
            new aws_iam.PolicyStatement({actions: ['s3:PutObject',
                    's3:GetBucketPolicy'],
                resources: [
                    bucket.bucketArn,
                    `${bucket.bucketArn}/*`],
                principals: [new aws_iam.ServicePrincipal('bcm-data-exports.amazonaws.com'),
                    new aws_iam.ServicePrincipal('billingreports.amazonaws.com'),
                ],
                conditions: {StringLike: {'aws:SourceAccount': `${this.account}`,
                        'aws:SourceArn': [`arn:aws:cur:${this.region}:${this.account}:definition/*`,
                            `arn:aws:bcm-data-exports:${this.region}:${this.account}:export/*`,
                        ]},
                },
            })
        )

        new aws_bcmdataexports.CfnExport(this, 'Cur', {export: {name: 'kakakakakku-cur',
                destinationConfigurations: {s3Destination: {s3Region: this.region,
                        s3Bucket: bucket.bucketName,
                        s3Prefix: 'cur',
                        s3OutputConfigurations: {format: 'PARQUET',
                            compression: 'PARQUET',
                            outputType: 'CUSTOM',
                            overwrite: 'OVERWRITE_REPORT',
                        },
                    },
                },
                dataQuery: {queryStatement: 'SELECT bill_bill_type, bill_billing_entity, bill_billing_period_end_date, bill_billing_period_start_date, bill_invoice_id, bill_invoicing_entity, bill_payer_account_id, bill_payer_account_name, cost_category, discount, discount_bundled_discount, discount_total_discount, identity_line_item_id, identity_time_interval, line_item_availability_zone, line_item_blended_cost, line_item_blended_rate, line_item_currency_code, line_item_legal_entity, line_item_line_item_description, line_item_line_item_type, line_item_net_unblended_cost, line_item_net_unblended_rate, line_item_normalization_factor, line_item_normalized_usage_amount, line_item_operation, line_item_product_code, line_item_tax_type, line_item_unblended_cost, line_item_unblended_rate, line_item_usage_account_id, line_item_usage_account_name, line_item_usage_amount, line_item_usage_end_date, line_item_usage_start_date, line_item_usage_type, pricing_currency, pricing_lease_contract_length, pricing_offering_class, pricing_public_on_demand_cost, pricing_public_on_demand_rate, pricing_purchase_option, pricing_rate_code, pricing_rate_id, pricing_term, pricing_unit, product, product_comment, product_fee_code, product_fee_description, product_from_location, product_from_location_type, product_from_region_code, product_instance_family, product_instance_type, product_instancesku, product_location, product_location_type, product_operation, product_pricing_unit, product_product_family, product_region_code, product_servicecode, product_sku, product_to_location, product_to_location_type, product_to_region_code, product_usagetype, reservation_amortized_upfront_cost_for_usage, reservation_amortized_upfront_fee_for_billing_period, reservation_availability_zone, reservation_effective_cost, reservation_end_time, reservation_modification_status, reservation_net_amortized_upfront_cost_for_usage, reservation_net_amortized_upfront_fee_for_billing_period, reservation_net_effective_cost, reservation_net_recurring_fee_for_usage, reservation_net_unused_amortized_upfront_fee_for_billing_period, reservation_net_unused_recurring_fee, reservation_net_upfront_value, reservation_normalized_units_per_reservation, reservation_number_of_reservations, reservation_recurring_fee_for_usage, reservation_reservation_a_r_n, reservation_start_time, reservation_subscription_id, reservation_total_reserved_normalized_units, reservation_total_reserved_units, reservation_units_per_reservation, reservation_unused_amortized_upfront_fee_for_billing_period, reservation_unused_normalized_unit_quantity, reservation_unused_quantity, reservation_unused_recurring_fee, reservation_upfront_value, resource_tags, savings_plan_amortized_upfront_commitment_for_billing_period, savings_plan_end_time, savings_plan_instance_type_family, savings_plan_net_amortized_upfront_commitment_for_billing_period, savings_plan_net_recurring_commitment_for_billing_period, savings_plan_net_savings_plan_effective_cost, savings_plan_offering_type, savings_plan_payment_option, savings_plan_purchase_term, savings_plan_recurring_commitment_for_billing_period, savings_plan_region, savings_plan_savings_plan_a_r_n, savings_plan_savings_plan_effective_cost, savings_plan_savings_plan_rate, savings_plan_start_time, savings_plan_total_commitment_to_date, savings_plan_used_commitment, line_item_resource_id FROM COST_AND_USAGE_REPORT',
                    tableConfigurations: {'COST_AND_USAGE_REPORT': {'INCLUDE_RESOURCES': 'TRUE',
                            'INCLUDE_SPLIT_COST_ALLOCATION_DATA': 'FALSE',
                            'TIME_GRANULARITY': 'DAILY',
                        },
                    },
                },
                refreshCadence: {
                    frequency: 'SYNCHRONOUS',
                },
            },
        }).node.addDependency(bucket)
    }
}

デプロイ確認

期待通りに CUR 2.0 エクスポートを設定できている👌

  • ファイルフォーマットは Parquetにする
  • エクスポートするコンテンツとして リソース IDを追加する
  • 粒度は 日次にする

ポイント

実装のポイントをメモしておこうと思う📝

1. リージョン

CUR 2.0 エクスポートは us-east-1リージョンに設定する必要がある🌍️

const envVirginia = {account: '000000000000',
    region: 'us-east-1',
}new CurStack(app, 'CurStack', {env: envVirginia })

2. リソース間の依存関係

最初デプロイしようとしたら Amazon S3 バケットポリシーのデプロイ前に CUR 2.0 エクスポートの設定をしようとしてエラーになる場合があった.今回は AWS CDK の .addDependency()を使って Amazon S3 バケットとバケットポリシーのデプロイ後に CUR 2.0 をデプロイするようにリソース間の依存関係を実装した.

AWS CloudFormation で CUR 2.0 をデプロイするサンプルコードは AWS re:Post で紹介されていて,同じように DependsOnが設定されていた👌

CURReportDefinition:DependsOn: S3ClientBucketAccessPolicy
  Type: AWS::BCMDataExports::Export

repost.aws

3. Amazon S3 バケットポリシー

Amazon S3 バケットポリシーは一度 CUR 2.0 をマネジメントコンソールで設定して,自動的に設定されるポリシーを参考にした📝

{"Version": "2012-10-17",
    "Statement": [{"Effect": "Allow",
            "Principal": {"Service": ["bcm-data-exports.amazonaws.com",
                    "billingreports.amazonaws.com"
                ]},
            "Action": ["s3:GetBucketPolicy",
                "s3:PutObject"
            ],
            "Resource": ["arn:aws:s3:::xxxxx",
                "arn:aws:s3:::xxxxx/*"
            ],
            "Condition": {"StringLike": {"aws:SourceAccount": "000000000000",
                    "aws:SourceArn": ["arn:aws:cur:us-east-1:000000000000:definition/*",
                        "arn:aws:bcm-data-exports:us-east-1:000000000000:export/*"
                    ]}}}]}

以下のドキュメントに載っている Amazon S3 バケットポリシーは Legacy CUR専用の設定になっていて,CUR 2.0 だと使えないため注意する😇 CUR 2.0 の Amazon S3 バケットポリシーもドキュメントに載せてくれると助かるんだけどなぁ...

docs.aws.amazon.com

4. S3OutputConfigurations

CUR 2.0 エクスポートの Amazon S3 に関連する設定 S3OutputConfigurationsで,今回は Parquet形式で出力するため formatcompressionPARQUETと設定している.overwriteには CREATE_NEW_REPORTOVERWRITE_REPORTを設定できて,今回は出力ファイルを上書きする OVERWRITE_REPORTを設定した👌

docs.aws.amazon.com

5. dataQuery

dataQuery.queryStatementにはコストデータを取得する SQL クエリを設定する.今回設定した SQL クエリは同じくマネジメントコンソールから取得してそのまま使っている👌運用上不要なカラムがあれば減らすこともできる.あと今回は dataQuery.tableConfigurations'INCLUDE_RESOURCES': 'TRUE'を設定して,エクスポートするコンテンツとして リソース IDを追加しているため line_item_resource_idカラムを含めた「計114カラム」になる❗️

SELECT bill_bill_type, bill_billing_entity, bill_billing_period_end_date, bill_billing_period_start_date, bill_invoice_id, bill_invoicing_entity, bill_payer_account_id, bill_payer_account_name, cost_category, discount, discount_bundled_discount, discount_total_discount, identity_line_item_id, identity_time_interval, line_item_availability_zone, line_item_blended_cost, line_item_blended_rate, line_item_currency_code, line_item_legal_entity, line_item_line_item_description, line_item_line_item_type, line_item_net_unblended_cost, line_item_net_unblended_rate, line_item_normalization_factor, line_item_normalized_usage_amount, line_item_operation, line_item_product_code, line_item_tax_type, line_item_unblended_cost, line_item_unblended_rate, line_item_usage_account_id, line_item_usage_account_name, line_item_usage_amount, line_item_usage_end_date, line_item_usage_start_date, line_item_usage_type, pricing_currency, pricing_lease_contract_length, pricing_offering_class, pricing_public_on_demand_cost, pricing_public_on_demand_rate, pricing_purchase_option, pricing_rate_code, pricing_rate_id, pricing_term, pricing_unit, product, product_comment, product_fee_code, product_fee_description, product_from_location, product_from_location_type, product_from_region_code, product_instance_family, product_instance_type, product_instancesku, product_location, product_location_type, product_operation, product_pricing_unit, product_product_family, product_region_code, product_servicecode, product_sku, product_to_location, product_to_location_type, product_to_region_code, product_usagetype, reservation_amortized_upfront_cost_for_usage, reservation_amortized_upfront_fee_for_billing_period, reservation_availability_zone, reservation_effective_cost, reservation_end_time, reservation_modification_status, reservation_net_amortized_upfront_cost_for_usage, reservation_net_amortized_upfront_fee_for_billing_period, reservation_net_effective_cost, reservation_net_recurring_fee_for_usage, reservation_net_unused_amortized_upfront_fee_for_billing_period, reservation_net_unused_recurring_fee, reservation_net_upfront_value, reservation_normalized_units_per_reservation, reservation_number_of_reservations, reservation_recurring_fee_for_usage, reservation_reservation_a_r_n, reservation_start_time, reservation_subscription_id, reservation_total_reserved_normalized_units, reservation_total_reserved_units, reservation_units_per_reservation, reservation_unused_amortized_upfront_fee_for_billing_period, reservation_unused_normalized_unit_quantity, reservation_unused_quantity, reservation_unused_recurring_fee, reservation_upfront_value, resource_tags, savings_plan_amortized_upfront_commitment_for_billing_period, savings_plan_end_time, savings_plan_instance_type_family, savings_plan_net_amortized_upfront_commitment_for_billing_period, savings_plan_net_recurring_commitment_for_billing_period, savings_plan_net_savings_plan_effective_cost, savings_plan_offering_type, savings_plan_payment_option, savings_plan_purchase_term, savings_plan_recurring_commitment_for_billing_period, savings_plan_region, savings_plan_savings_plan_a_r_n, savings_plan_savings_plan_effective_cost, savings_plan_savings_plan_rate, savings_plan_start_time, savings_plan_total_commitment_to_date, savings_plan_used_commitment, line_item_resource_id FROM COST_AND_USAGE_REPORT

ちなみに dataQuery.tableConfigurationsに関しては AWS CloudFormation のドキュメントを見ても Type: Object of String以外の情報がなく困ったけど,同じくマネジメントコンソールの設定を参考にした.今回コストデータの集計単位 TIME_GRANULARITYは日次 DAILYにしてある.

tableConfigurations: {'COST_AND_USAGE_REPORT': {'INCLUDE_RESOURCES': 'TRUE',
        'INCLUDE_SPLIT_COST_ALLOCATION_DATA': 'FALSE',
        'TIME_GRANULARITY': 'DAILY',
    },
},

docs.aws.amazon.com

まとめ

CUR 2.0 でコスト分析をやっていくぞ〜💪


2024年の振り返りと2025年の抱負

$
0
0

2024年の振り返り🎉

お仕事を楽しく頑張って貢献できた

2024年の年間目標として「お仕事を楽しく頑張って貢献する」を掲げていた.2023年4月からフリーランスのソフトウェアエンジニアになって,1年半以上活動しているけど,本当に毎日楽しく働けている👌

2024年からは大きく2つの案件に参画していて,1つは新規プロダクト開発の技術面をほぼ1人で担当していて,もう1つは歴史のある既存プロダクトと開発組織のモダン化を支援していた💪 アーキテクチャ設計・バックエンド開発・インフラ構築・コスト最適化・DevOps 推進・自動テスト導入・エンジニア育成などなど幅広く担当していて,個人的にあまり得意ではないけどフロントエンド開発もやった.2024年7月からは稼働時間も増えて,1ヶ月に200時間以上は稼働していたけど,とにかく毎日楽しくてツライと感じることはあまりなかった❗️(もちろん忙しすぎる月はあったけど...)

たくさん働くということは「たくさん技術に触れる機会がある」ということだし,同時に「たくさんハマるポイントもある」ということで,結果的にテックブログネタの宝庫にもなる💎テックブログに関しては後述するけど,2024年は「117記事」も書けて2016年以来の最高記録になったのも楽しんでいた証だな〜と \( 'ω')/

2017年に発表した「ブログを書く技術」から引用

あとお仕事で使うウェブサイトも作った.今のところお仕事を募集するためのサイトという位置付けで,空いている稼働枠に変化があった場合に更新している📝 ちなみに技術的には Docusaurus + GitHub Pages で作った.モダンな技術スタックを使った案件も好きだし,プロダクトも開発組織もレガシーでモダン化を推進する案件も好きなので,楽しそうなお仕事があったらご連絡いただければと🙏

kakakakakku.github.io

LocalStack の普及に取り組んでいた

2024年は LocalStack をたくさん使ってたくさん普及していた👌 LocalStack 自体は数年前から知っていて,実際に Amazon S3 と Amazon SQS のエミュレーターとして使ったことがあったけど,LocalStack をローカル環境と自動テスト環境に導入している案件があって,改めて LocalStack の活用を模索してみたところ,改めて便利さに感動した.もっと多くの人に知ってもらいたいと思ってアウトプットも頑張っていた.

LocalStack に関するブログ記事も10記事以上書いたけど,ある程度まとまったコンテンツとして Zenn Book に「LocalStack 実践入門 | AWS アプリケーション開発ワークショップ」「LocalStack 実践入門 | AWS サーバレスパターン開発ワークショップ」を公開した.予想以上に多くの人に読んでもらえて,感想ももらえて,Zenn Book って結構影響力あるんだな〜ということにも気付けた📕

zenn.dev

zenn.dev

技術を学び続けた

そして,2024年の四半期ごとに掲げていた中目標も紹介する.基本的には AWS 関連のインプット/アウトプットを軸に日々取り組んでいて,例えば AWS CDK / Terraform AWS Provider / Powertools for AWS Lambda (Python) / AWS ワークショップ紹介などの記事をたくさん書けた📝 前述した Zenn Book の執筆も中目標に掲げていた.

  • 1-3月 : AWS のノウハウを積極的にアウトプットする
  • 4-6月 : AWS のノウハウを積極的にアウトプットする
  • 7-9月 : LocalStack 実践入門(AWS アプリケーション開発ワークショップ)を Zenn Book に公開する
  • 10-12月 : LocalStack 実践入門(AWS サーバレスパターン開発ワークショップ)を Zenn Book に公開する

他にも仕事で必要になった技術や興味を持った技術もたくさんあって,ずっとインプット/アウトプットをしていたな〜と思う😀

登壇する機会もあった

2024年5月には Terraform 関連のイベントでモデレーターを担当する機会もあった🎤 直近数年はイベントに参加する機会が減ってしまっているけど,久し振りにパブリックに話せて良かった❗️正直準備も大変だったし,当日も異常に緊張したし,頻繁にできるものではないな〜とは思うけど...😇 あとイベント当日は金髪になっていて,イベントに参加していた友人からは「誰だよw」って突っ込まれたりもした(笑)今も YouTube で観れるので良かったらどうぞ \( 'ω')/

www.youtube.com

kakakakakku.hatenablog.com

Write Code Every Day

特に習慣化のテーマではなかったけど,ほぼ毎日仕事・個人プロジェクト・勉強をしていたため,結果的に2024年は Write Code Every Dayになっていた🌸 2025年も継続できればな〜と.

2024年に導入して良かった技術/ツールなど

インプット/アウトプット💡

テックブログ

2024年は「週1記事(52記事)」のノルマとストレッチゴールの「100記事」を掲げていた.ノルマを達成できなかった週はなく,さらに目標を大幅に超えて「年間117記事」を達成できた❗️それほどに2024年は多くのインプットとアウトプットを楽しめた1年間だった.テックブログ最高〜 \( 'ω')/

累計ブクマ数は「20687 → 21166 (+479)」となり,大きく伸びなかった.基本的に個人的な技術検証ログを中心にアウトプットしているため,瞬間的に伸びるとは思ってなく,検索流入で「誰かのためになればイイな〜」と思ってテックブログを書いている😀 個人的にはイイ記事をたくさん書けた1年だった \( 'ω')/

グラフ的に少し変化している箇所は「GitHub CI/CD 本の書評」「エンジニアリングマネジャー本の書評」「LangChain / LangGraph 本の書評」「GitHub Actions の concurrency 紹介」だった.書評記事は伸びるんだな〜📕

プルリクエスト

詳しくは振り返り記事にまとめた.

kakakakakku.hatenablog.com

読んだ本

詳しくは振り返り記事にまとめた.

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

YouTube

詳しくは振り返り記事にまとめた.

kakakakakku.hatenablog.com

買って良かったモノ🎁

2024年はほとんどガジェット系は買ってないけど,2024年4月に買ったデスクライト「BenQ ScreenBar Halo」は特に買って良かった❗️最高〜と思う.本当におすすめできる.

2025年の抱負✨

お仕事を楽しく頑張って貢献する

2025年も引き続きソフトウェア開発全般の支援を楽しみつつ案件に貢献したいと思う💪新規プロダクト開発や既存プロダクトのモダン化もやりつつ,2025年は技術顧問としてのお仕事も既に決まっていて,技術組織を客観的に見極めつつの支援も楽しもうと思う.

そのためには僕自身も常に成長し続ける必要があって,インプット/アウトプットを続けていく.2025年も「週1記事」をノルマにしつつ,ストレッチゴールの「年間100記事」も目指す📝2024年に「年間117記事」も書いたことによって達成できるという感覚が既にあるw

  • ブログ「週1記事(52記事)」ノルマ
  • ストレッチゴールとして1年間で「100記事」

"LocalStack 実践入門" の新作を執筆する

テックブログは個人的な検証ログや学習ログをアウトプットする場として活用しつつ,2025年も Zenn Book に "LocalStack 実践入門"の新作を執筆したいと思う.既に企画の洗い出しには着手している❗️既に公開している Zenn Book に対して「Like・応援購入・バッジ」をもらえることは本当にモチベーションにつながっていて感謝だ〜😀

その他

YouTube も引き続き取り組んでいくし,ブログには書けないけど他にも大きめの年間目標があったりもする.頑張っていくぞー💪

まとめ

2025年も楽しむぞ〜❗️よろしくお願いしまーすッッッ \( 'ω')/

過去の振り返り

GitHub Codespaces prebuilds(プレビルド)を使って GitHub Codespaces の起動時間を速くする

$
0
0

Zenn Book で公開している「LocalStack 実践入門 | AWS アプリケーション開発ワークショップ」「LocalStack 実践入門 | AWS サーバレスパターン開発ワークショップ」ではワークショップ環境として GitHub Codespacesを使っている💡できる限りセットアップの負荷を下げて,誰もが同じ環境でサクッと実施できるようにしている👌

zenn.dev

zenn.dev

課題

しかし GitHub Codespaces を起動するときにコンテナ (devcontainers) のビルドをするため,ワークショップ環境のセットアップに少し待ち時間が発生してしまうという課題があった💨 特に「LocalStack 実践入門 | AWS サーバレスパターン開発ワークショップ」では LocalStack CLI / LocalStack AWS CLI / LocalStack AWS SAM CLI もセットアップしているため,少し長くなってしまう.

そして,どちらかと言えば初学者向けに執筆しているため,セットアップの待ち時間がワークショップの離脱ポイントになったら嫌だな〜という不安もあった😇

計測

GitHub Codespaces のビルドログから時間を計測してみた⌛️

個人的な体感ではもう少し長いかな〜と思っていた.

aws-application-workshop-using-localstack

  • 1回目: 2分54秒
  • 2回目: 1分57秒
$ egrep'Creating container...|Finished configuring codespace.' /workspaces/.codespaces/.persistedshare/creation.log
2025-01-03 03:47:12.819Z: Creating container...
2025-01-03 03:50:06.007Z: Finished configuring codespace.

$ egrep'Creating container...|Finished configuring codespace.' /workspaces/.codespaces/.persistedshare/creation.log
2025-01-03 21:43:53.092Z: Creating container...
2025-01-03 21:45:50.808Z: Finished configuring codespace.

aws-serverless-pattern-workshop-using-localstack

  • 1回目: 3分40秒
  • 2回目: 3分30秒
$ egrep'Creating container...|Finished configuring codespace.' /workspaces/.codespaces/.persistedshare/creation.log
2025-01-03 03:47:17.704Z: Creating container...
2025-01-03 03:50:57.604Z: Finished configuring codespace.

$ egrep'Creating container...|Finished configuring codespace.' /workspaces/.codespaces/.persistedshare/creation.log
2025-01-03 21:55:40.029Z: Creating container...
2025-01-03 21:59:10.466Z: Finished configuring codespace.

GitHub Codespaces prebuilds

GitHub Codespaces には GitHub Codespaces prebuilds(プレビルド)という機能がある.簡単に言うと,事前に GitHub Codespaces 環境をビルドしておいて,必要になったらすぐに起動できるという仕組みで,ドキュメントに詳しく載っている👌

docs.github.com

導入するべき条件として「ビルドに2分以上」と書いてあって,今回は該当しそうだった📝

If it currently takes more than 2 minutes to create a codespace for a repository, you are likely to benefit from using prebuilds.

なお,ドキュメントには GitHub Codespaces のストレージ課金の注意点が書いてあったけど,実際に Billing summary も確認して無料利用枠(GitHub Pro プラン)で問題なさそうだった.

docs.github.com

プレビルド設定

プレビルド設定はリポジトリ画面でポチポチする必要があって,.github/ディレクトリに設定ファイルを置くことはできなかった.今回はデフォルト設定のままにした.

  • プレビルドの対象ブランチは main
  • プレビルドのトリガーは mainブランチに対する push
  • プレビルドを保存するリージョンは Southeast Asiaのみ
  • ストレージ使用量を抑えるため保持するバージョンは 1

GitHub Actions

プレビルドを設定すると自動的に GitHub Actions ワークフローが実行される.GitHub Codespaces 環境をビルドした後に codespaces prebuild uploadコマンドでプレビルドをアップロードしていたり,内部的な挙動も少し見え隠れしていた👀

/.codespaces/agent/bin/codespaces prebuild upload --storage-type v2 --target-locations CentralIndia --target-locations SouthEastAsia --repo-name kakakakakku/aws-serverless-pattern-workshop-using-localstack --devcontainer-path$DEVCONTAINER_PATH--config-id$CONFIGURATION_ID--flush-only--image-version Raw --features-env FEATURE_FLAGS_JSON

プレビルド効果

再度 GitHub Codespaces のビルドログから時間を計測してみたところ,なんと爆速で起動できるようになった❗️

aws-application-workshop-using-localstack

  • 1回目: 10秒 🎉
  • 2回目: 10秒 🎉
$ egrep'Creating container...|Finished configuring codespace.' /workspaces/.codespaces/.persistedshare/creation.log
2025-01-05 00:32:20.970Z: Creating container...
2025-01-05 00:34:06.618Z: Finished configuring codespace.
2025-01-05 11:11:07.996Z: Creating container...
2025-01-05 11:11:17.251Z: Finished configuring codespace.

$ egrep'Creating container...|Finished configuring codespace.' /workspaces/.codespaces/.persistedshare/creation.log
2025-01-05 00:32:20.970Z: Creating container...
2025-01-05 00:34:06.618Z: Finished configuring codespace.
2025-01-05 11:17:37.527Z: Creating container...
2025-01-05 11:17:47.075Z: Finished configuring codespace.

aws-serverless-pattern-workshop-using-localstack

  • 1回目: 8秒 🎉
  • 2回目: 9秒 🎉
$ egrep'Creating container...|Finished configuring codespace.' /workspaces/.codespaces/.persistedshare/creation.log
2025-01-05 00:01:33.305Z: Creating container...
2025-01-05 00:05:04.747Z: Finished configuring codespace.
2025-01-05 11:11:16.128Z: Creating container...
2025-01-05 11:11:24.384Z: Finished configuring codespace.

$ egrep'Creating container...|Finished configuring codespace.' /workspaces/.codespaces/.persistedshare/creation.log
2025-01-05 00:01:33.305Z: Creating container...
2025-01-05 00:05:04.747Z: Finished configuring codespace.
2025-01-05 11:21:37.582Z: Creating container...
2025-01-05 11:21:46.436Z: Finished configuring codespace.

まとめ

GitHub Codespaces prebuilds(プレビルド)を使って GitHub Codespaces の起動時間を速くできた👏

もし興味を持ってもらえたら「LocalStack 実践入門 | AWS アプリケーション開発ワークショップ」「LocalStack 実践入門 | AWS サーバレスパターン開発ワークショップ」を試してもらえればと〜🙏

zenn.dev

zenn.dev

関連記事

kakakakakku.hatenablog.com

DuckDB: bar 関数で棒グラフを作る

$
0
0

DuckDB in Actionを読んでいたら,Chapter.1 / Chapter.10 に「bar 関数」が出てきた📊 たくさん組み込み関数があるんだな〜と思って実際に試してみた❗️小ネタだけど検証ログを残しておく✍

ちなみに DuckDB のドキュメントだと Text Functionsのページに載っていた.

bar(x, min, max[, width])

duckdb.org

お試し1

WITHで映画のレーティングデータを適当に作って bar()で棒グラフを作ってみた📊

⚫◗ WITH ratings(movie, rating) AS (
    VALUES 
        ('Movie A', 4.5),
        ('Movie B', 3.8),
        ('Movie C', 4.9),
        ('Movie D', 2.1),
        ('Movie E', 3.3)
)
SELECT
    movie,
    rating,
    bar(rating, 0, 5) AS bar
FROM ratings;
┌─────────┬──────────────┬─────────────────────────────────────────────────────────────────────────────────┐
│  movie  │    rating    │                                       bar                                       │
│ varchardecimal(2,1) │                                     varchar│
├─────────┼──────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ Movie A │          4.5│ ████████████████████████████████████████████████████████████████████████        │
│ Movie B │          3.8│ ████████████████████████████████████████████████████████████▊                   │
│ Movie C │          4.9│ ██████████████████████████████████████████████████████████████████████████████▍ │
│ Movie D │          2.1│ █████████████████████████████████▌                                              │
│ Movie E │          3.3│ ████████████████████████████████████████████████████▊                           │
└─────────┴──────────────┴─────────────────────────────────────────────────────────────────────────────────┘

お試し2

個人的な検証用 AWS アカウントで AWS Data ExportsCost and Usage Reports 2.0 (CUR 2.0)データを Parquet 形式でエクスポートしている.kakakakakku-cur-00001.snappy.parquetをローカルに落としてきて,2025/01/01から 2025/01/07の1週間でサービス別の合計金額から棒グラフを作ってみた📊

検証する日以外は基本的に課金されないようにしていて,棒グラフの結果は微妙だけど,期待した結果が取得できている👌

SELECT
    line_item_product_code,
    SUM(CAST(line_item_unblended_cost ASDECIMAL(16,8))) AS sum_line_item_unblended_cost,
    bar(sum_line_item_unblended_cost, 0, 0.6) AS bar
FROM'kakakakakku-cur-00001.snappy.parquet'WHERE STRFTIME(line_item_usage_start_date, '%Y-%m-%d') BETWEEN'2025-01-01'AND'2025-01-07'GROUPBY
    line_item_product_code
ORDERBY
    sum_line_item_unblended_cost DESC;
┌────────────────────────┬──────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────┐
│ line_item_product_code │ sum_line_item_unblended_cost │                                       bar                                        │
│        varchardecimal(38,8)         │                                     varchar│
├────────────────────────┼──────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┤
│ AmazonRoute53          │                   0.55000000│ █████████████████████████████████████████████████████████████████████████▎       │
│ AmazonS3               │                   0.00122256│ ▏                                                                                │
│ AWSCloudShell          │                   0.00000116│                                                                                  │
│ awskms                 │                   0.00000000│                                                                                  │
│ AWSCloudFormation      │                   0.00000000│                                                                                  │
│ AWSEvents              │                   0.00000000│                                                                                  │
│ AWSDataTransfer        │                   0.00000000│                                                                                  │
│ AmazonCloudWatch       │                   0.00000000│                                                                                  │
└────────────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────┘

関連情報

AWS CDK で Cost and Usage Reports 2.0 (CUR 2.0) エクスポートを設定する📝

kakakakakku.hatenablog.com

CUR 2.0 データからコスト分析をするときのクエリ例を探すときは「AWS Well-Architected Cost & Usage Report Library」が参考になる📝(一部 CUR 2.0 に対応していなかったりするけど Legacy CUR を読み替えれば OK👌)

catalog.workshops.aws

CUR 2.0 データのカラムの意味を確認する場合はドキュメント参照📝

docs.aws.amazon.com

DuckDB: SELECT * EXCLUDE で 一部のカラムを除外する

$
0
0

DuckDB in Actionを読んでいたら,Chapter.3 に SELECT * EXCLUDEというシンタックスが出てきた.ほとんどのカラムが必要だけど,一部のカラムだけ除外したいときに使える.これは使える場面があるな〜と思って実際に試してみた❗️小ネタだけど検証ログを残しておく✍

データセット

DuckDB in Actionを読みながら試せるデータセットは GitHub に公開されている.

github.com

今回は ch03/ch03_db/prices.parquetを使う.データ構造は以下のようになっている👌

⚫◗ DESCRIBE SELECT * FROM prices.parquet;
┌─────────────┬──────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type  │  null   │   key   │ default │  extra  │
│   varchar   │   varchar    │ varchar │ varchar │ varchar │ varchar │
├─────────────┼──────────────┼─────────┼─────────┼─────────┼─────────┤
│ id          │ INTEGER      │ YES     │         │         │         │
│ value       │ DECIMAL(5,2)│ YES     │         │         │         │
│ valid_from  │ DATE         │ YES     │         │         │         │
│ valid_until │ DATE         │ YES     │         │         │         │
└─────────────┴──────────────┴─────────┴─────────┴─────────┴─────────┘

お試し1

まずは SELECT *で全レコードを取得する.

⚫◗ SELECT * FROM prices.parquet;
┌───────┬──────────────┬────────────┬─────────────┐
│  id   │    value     │ valid_from │ valid_until │
│ int32 │ decimal(5,2) │    datedate│
├───────┼──────────────┼────────────┼─────────────┤
│     111.592018-12-012019-01-01│
│    1011.472019-01-012019-02-01│
│    1111.352019-02-012019-03-01│
│    1211.232019-03-012019-04-01│
│    1311.112019-04-012019-05-01│
│    1410.952019-05-012019-06-01│
│    158.602020-11-012023-01-01│
│    168.602023-01-012024-02-01│
│    178.642020-10-012020-11-01│
│    188.772020-09-012020-10-01│
│    198.902020-08-012020-09-01│
│    209.032020-07-012020-08-01│
│    219.172020-06-012020-07-01│
│    229.302020-05-012020-06-01│
│    239.442020-04-012020-05-01│
│    249.582020-03-012020-04-01│
│    259.722020-02-012020-03-01│
│    269.872020-01-012020-02-01│
│    279.972019-12-012020-01-01│
│    2810.082019-11-012019-12-01│
│    2910.182019-10-012019-11-01│
│    3010.332019-09-012019-10-01│
│    3110.482019-08-012019-09-01│
│    3210.642019-07-012019-08-01│
│    3310.792019-06-012019-07-01│
├───────┴──────────────┴────────────┴─────────────┤
│ 25rows4 columns │
└─────────────────────────────────────────────────┘

お試し2

次に idカラムを除外して全レコードを取得する.今回試す SELECT * EXCLUDEを使わない場合は,個別に valuevalid_fromvalid_untilを指定する必要がある.今回のデータセットはカラム数が少なくて問題なく感じるけど,もっとカラム数が増えてくると,個別に指定する大変さがある💨

⚫◗ SELECT value, valid_from, valid_until FROM prices.parquet;
┌──────────────┬────────────┬─────────────┐
│    value     │ valid_from │ valid_until │
│ decimal(5,2) │    datedate│
├──────────────┼────────────┼─────────────┤
│        11.592018-12-012019-01-01│
│        11.472019-01-012019-02-01│
│        11.352019-02-012019-03-01│
│        11.232019-03-012019-04-01│
│        11.112019-04-012019-05-01│
│        10.952019-05-012019-06-01│
│         8.602020-11-012023-01-01│
│         8.602023-01-012024-02-01│
│         8.642020-10-012020-11-01│
│         8.772020-09-012020-10-01│
│         8.902020-08-012020-09-01│
│         9.032020-07-012020-08-01│
│         9.172020-06-012020-07-01│
│         9.302020-05-012020-06-01│
│         9.442020-04-012020-05-01│
│         9.582020-03-012020-04-01│
│         9.722020-02-012020-03-01│
│         9.872020-01-012020-02-01│
│         9.972019-12-012020-01-01│
│        10.082019-11-012019-12-01│
│        10.182019-10-012019-11-01│
│        10.332019-09-012019-10-01│
│        10.482019-08-012019-09-01│
│        10.642019-07-012019-08-01│
│        10.792019-06-012019-07-01│
├──────────────┴────────────┴─────────────┤
│ 25rows3 columns │
└─────────────────────────────────────────┘

お試し3

そこで SELECT * EXCLUDEを使うと,除外するカラムを指定できる👌

⚫◗ SELECT * EXCLUDE (id) FROM prices.parquet;
┌──────────────┬────────────┬─────────────┐
│    value     │ valid_from │ valid_until │
│ decimal(5,2) │    datedate│
├──────────────┼────────────┼─────────────┤
│        11.592018-12-012019-01-01│
│        11.472019-01-012019-02-01│
│        11.352019-02-012019-03-01│
│        11.232019-03-012019-04-01│
│        11.112019-04-012019-05-01│
│        10.952019-05-012019-06-01│
│         8.602020-11-012023-01-01│
│         8.602023-01-012024-02-01│
│         8.642020-10-012020-11-01│
│         8.772020-09-012020-10-01│
│         8.902020-08-012020-09-01│
│         9.032020-07-012020-08-01│
│         9.172020-06-012020-07-01│
│         9.302020-05-012020-06-01│
│         9.442020-04-012020-05-01│
│         9.582020-03-012020-04-01│
│         9.722020-02-012020-03-01│
│         9.872020-01-012020-02-01│
│         9.972019-12-012020-01-01│
│        10.082019-11-012019-12-01│
│        10.182019-10-012019-11-01│
│        10.332019-09-012019-10-01│
│        10.482019-08-012019-09-01│
│        10.642019-07-012019-08-01│
│        10.792019-06-012019-07-01│
├──────────────┴────────────┴─────────────┤
│ 25rows3 columns │
└─────────────────────────────────────────┘

もちろん複数カラムを除外することもできる👌

⚫◗ SELECT * EXCLUDE (id, value) FROM prices.parquet;
┌────────────┬─────────────┐
│ valid_from │ valid_until │
│    datedate│
├────────────┼─────────────┤
│ 2018-12-012019-01-01│
│ 2019-01-012019-02-01│
│ 2019-02-012019-03-01│
│ 2019-03-012019-04-01│
│ 2019-04-012019-05-01│
│ 2019-05-012019-06-01│
│ 2020-11-012023-01-01│
│ 2023-01-012024-02-01│
│ 2020-10-012020-11-01│
│ 2020-09-012020-10-01│
│ 2020-08-012020-09-01│
│ 2020-07-012020-08-01│
│ 2020-06-012020-07-01│
│ 2020-05-012020-06-01│
│ 2020-04-012020-05-01│
│ 2020-03-012020-04-01│
│ 2020-02-012020-03-01│
│ 2020-01-012020-02-01│
│ 2019-12-012020-01-01│
│ 2019-11-012019-12-01│
│ 2019-10-012019-11-01│
│ 2019-09-012019-10-01│
│ 2019-08-012019-09-01│
│ 2019-07-012019-08-01│
│ 2019-06-012019-07-01│
├────────────┴─────────────┤
│ 25rows2 columns │
└──────────────────────────┘

関連記事

kakakakakku.hatenablog.com

Terraform S3 Backend でステートロックのための DynamoDB が不要になる use_lockfile = true

$
0
0

Terraform v1.10.0 で導入された S3 Backend の use_lockfileオプションを使うと,Amazon S3 バケットで tfstate を管理しつつ,Amazon S3 のネイティブ機能 (conditional writes) でステートロックも実現できる👌今までステートロックのために使っていた Amazon DynamoDB が不要になるというメリットがある❗️

github.com

Terraform v1.10.0 は2024年11月にリリースされて,use_lockfileオプション自体もまだ Experimental(実験的)ではあるけど,既に use_lockfileオプションが Experimental ではなくなって,今度は逆に Amazon DynamoDB を使ったステートロックの仕組みが Deprecated(非推奨)になるというプルリクエストが merge されている📝

github.com

そして Terraform v1.11.0-beta1 のリリースノートに S3 native state locking is now generally available.と載っていた.よって,Terraform v1.11.0 からは use_lockfileオプションを使うことが一般的になりそう.

github.com

最近 S3 Backend の use_lockfileオプションを紹介する機会があって,デモ環境を作ったため,簡単にまとめておこうと思う👌

use_lockfileオプションを試す

Amazon S3 バケットを作る

まず,tfstate を管理する Amazon S3 バケットを作る🗑️

$ aws s3api create-bucket \--bucket kakakakakku-sandbox-tfstates \--create-bucket-configurationLocationConstraint=ap-northeast-1
$ aws s3api put-bucket-versioning \--bucket kakakakakku-sandbox-tfstates \--versioning-configurationStatus=Enabled

ドキュメントに警告として載っている通り,Amazon S3 バケットのバージョニング機能も有効化しておく👌あと今回は割愛しているけど,ライフサイクルポリシーで古くなったバージョニングを消しておくと良いと思う.

Warning! It is highly recommended that you enable Bucket Versioning on the S3 bucket to allow for state recovery in the case of accidental deletions and human error.

developer.hashicorp.com

👾 backend.tf

backend.tfは以下のようにした.ポイントは S3 Backend で use_lockfile = trueを設定しているところ💡

terraform{backend"s3"{region       = "ap-northeast-1"bucket       = "kakakakakku-sandbox-tfstates"key          = "terraform.tfstate"use_lockfile = true}}

👾 main.tf

main.tfは何でも良くて,今回は Amazon CloudWatch ロググループを作る📝

resource"aws_cloudwatch_log_group""main"{name = "sandbox-use-lockfile"}

init / plan / apply(1回目)

普段通りに init / plan を実行する.

$ terraform init
Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
(中略)
Terraform has been successfully initialized!

$ terraform plan
(中略)
Plan: 1 to add, 0 to change, 0 to destroy.

そして次に apply を実行して Enter a valueを入力するときにステートロックを確認できる👌

$ terraform apply
(中略)
Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 

yesを入力せず Amazon S3 バケットを確認すると terraform.tfstate.tflockファイルが保存されていた❗️(正確には plan 実行時にも terraform.tfstate.tflockファイルは作られていて,バージョニングを確認するとわかる)

最後に yesを入力して apply を完了しておく.

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

すると今度は Amazon S3 バケットに terraform.tfstateファイルが保存されて,terraform.tfstate.tflockファイルは削除されていた👌

plan / apply(2回目)

今度はステートロックを確認する.Amazon CloudWatch Logs に retention_in_days = 7を追加する.

resource"aws_cloudwatch_log_group""main"{name              = "sandbox-use-lockfile"retention_in_days = 7}

同じく plan / apply を実行して,Enter a valueで止めておく🛑

$ terraform plan
Plan: 0 to add, 1 to change, 0 to destroy.

$ terraform apply
(中略)
Plan: 0 to add, 1 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

そして別のターミナルから plan を実行すると Error: Error acquiring the state lockというエラーが出てステートロックを確認できた👏 期待通り〜 \( 'ω')/

$ terraform plan
╷
│ Error: Error acquiring the state lock
│ 
│ Error message: operation error S3: PutObject, https response error StatusCode: 412, RequestID: AXHQ4SP42S1DE2XQ, HostID: Xf6uqAz2Oo+c2+QXKdrL0tzBi6GKHVFjCg6gz/WH9HXt/yZEm9f+82en89RHGRZzoWvQSxjCifQ=, api error PreconditionFailed: At least one of the
│ pre-conditions you specified did not hold
│ Lock Info:
│   ID:        41f52805-3896-a963-70e3-a4bc02de9ea8
│   Path:      kakakakakku-sandbox-tfstates/terraform.tfstate
│   Operation: OperationTypeApply
│   Who:       kakakakakku@MacBookAir.local│   Version:   1.10.3│   Created:   2025-01-12 01:48:20.240611 +0000 UTC
│   Info:      
│ 
│ 
│ Terraform acquires a state lock to protect the state from being written
│ by multiple users at the same time. Please resolve the issue above and try
│ again. For most commands, you can disable locking with the "-lock=false"│ flag, but this is not recommended.
╵

さらにエラーを確認すると「ステータスコード 412」と「メッセージ PreconditionFailed」になっていて,Amazon S3 の conditional writes(条件付き書き込み)のレスポンスになっていることもわかる.

docs.aws.amazon.com

Amazon DynamoDB を使わずにステートロックを実現できた❗️

仕組み

ステートロックの仕組みとしては,2024年8月にリリースされた Amazon S3 の conditional writes(条件付き書き込み)を使っていて,terraform.tfstate.tflockファイルを保存するときの条件として --if-none-matchを指定している.よって,Amazon S3 バケットに既に terraform.tfstate.tflockファイルが存在していたら保存に失敗して,競合を検知できるようになっている.

aws.amazon.com

実際の Terraform の実装だと internal/backend/remote-state/s3/client.goが参考になる📝

input := &s3.PutObjectInput{
    ContentType: aws.String("application/json"),
    Body:        bytes.NewReader(lockFileJson),
    Bucket:      aws.String(c.bucketName),
    Key:         aws.String(c.lockFilePath),
    IfNoneMatch: aws.String("*"),
}

まとめ

Terraform v1.10.0 で導入された S3 Backend の use_lockfileオプションを使うと,ステートロックのために使っていた Amazon DynamoDB が不要になるので覚えておこう👌

関連ドキュメント

developer.hashicorp.com

tflocal を使って Terraform から LocalStack にデプロイしよう

$
0
0

Terraform から LocalStack にデプロイする場合,以下のように provider.tfprovider設定で LocalStack のエンドポイントを参照するように実装する必要がある💡

provider"aws"{region     = "ap-northeast-1"access_key = "DUMMY"secret_key = "DUMMY"s3_use_path_style           = trueskip_requesting_account_id  = trueskip_credentials_validation = trueskip_metadata_api_check     = trueendpoints{s3 = "http://localhost:4566"}
}

しかし LocalStack のために設定が必要なのは微妙だったりもして,そんなときは tflocalコマンドが便利❗️tflocalコマンドは LocalStack 公式のツールで,他にも LocalStack AWS CLI(awslocalコマンド)や LocalStack AWS SAM CLI(samlocalコマンド)もあって,同じように Terraform 専用の LocalStack ツールと言える👌

github.com

docs.localstack.cloud

セットアップ

pipコマンドでセットアップできる.

$ pip install terraform-local

もしくは Homebrew でセットアップすることもできる.

$ brew install terraform-local

formulae.brew.sh

localstack_providers_override.tf

tflocalコマンドは terraformコマンドを実行する前に,一時的に localstack_providers_override.tfというファイルを生成して,terraformコマンドの実行後に自動的にファイルを消す仕組みになっている.tflocalコマンドの環境変数 DRY_RUNを使えば localstack_providers_override.tfを確認できる📝

$ DRY_RUN=1 tflocal

👾 localstack_providers_override.tf

Terraform はプロジェクトに *_override.tfという命名規則に沿ったファイルがあると上書きして読み込む仕組みがあって,tflocalコマンドが生成する localstack_providers_override.tfはその命名規則に沿っている.

developer.hashicorp.com

実際に以下のファイルが生成される.各サービスで LocalStack のエンドポイントを参照するようになっていて,他には access_keysecret_keyも自動的に設定されるようになっている👌

provider"aws"{access_key                  = "test"secret_key                  = "test"skip_credentials_validation = trueskip_metadata_api_check     = trueregion = "ap-northeast-1"endpoints{acm = "http://localhost:4566"amplify = "http://localhost:4566"apigateway = "http://localhost:4566"apigatewayv2 = "http://localhost:4566"appautoscaling = "http://localhost:4566"appconfig = "http://localhost:4566"appflow = "http://localhost:4566"appsync = "http://localhost:4566"athena = "http://localhost:4566"autoscaling = "http://localhost:4566"backup = "http://localhost:4566"batch = "http://localhost:4566"ce = "http://localhost:4566"cloudformation = "http://localhost:4566"cloudfront = "http://localhost:4566"cloudsearch = "http://localhost:4566"cloudtrail = "http://localhost:4566"cloudwatch = "http://localhost:4566"codecommit = "http://localhost:4566"cognitoidentity = "http://localhost:4566"cognitoidp = "http://localhost:4566"configservice = "http://localhost:4566"docdb = "http://localhost:4566"dynamodb = "http://localhost:4566"ec2 = "http://localhost:4566"ecr = "http://localhost:4566"ecs = "http://localhost:4566"efs = "http://localhost:4566"eks = "http://localhost:4566"elasticache = "http://localhost:4566"elasticbeanstalk = "http://localhost:4566"elasticsearch = "http://localhost:4566"elb = "http://localhost:4566"elbv2 = "http://localhost:4566"emr = "http://localhost:4566"events = "http://localhost:4566"firehose = "http://localhost:4566"fis = "http://localhost:4566"glacier = "http://localhost:4566"glue = "http://localhost:4566"iam = "http://localhost:4566"iot = "http://localhost:4566"iotanalytics = "http://localhost:4566"iotevents = "http://localhost:4566"kafka = "http://localhost:4566"keyspaces = "http://localhost:4566"kinesis = "http://localhost:4566"kinesisanalytics = "http://localhost:4566"kinesisanalyticsv2 = "http://localhost:4566"kms = "http://localhost:4566"lakeformation = "http://localhost:4566"lambda = "http://localhost:4566"logs = "http://localhost:4566"mediaconvert = "http://localhost:4566"mediastore = "http://localhost:4566"mq = "http://localhost:4566"mwaa = "http://mwaa.localhost.localstack.cloud:4566"neptune = "http://localhost:4566"opensearch = "http://localhost:4566"organizations = "http://localhost:4566"pinpoint = "http://localhost:4566"pipes = "http://localhost:4566"qldb = "http://localhost:4566"ram = "http://localhost:4566"rds = "http://localhost:4566"redshift = "http://localhost:4566"redshiftdata = "http://localhost:4566"resourcegroups = "http://localhost:4566"resourcegroupstaggingapi = "http://localhost:4566"route53 = "http://localhost:4566"route53domains = "http://localhost:4566"route53resolver = "http://localhost:4566"s3 = "http://s3.localhost.localstack.cloud:4566"s3control = "http://localhost:4566"sagemaker = "http://localhost:4566"scheduler = "http://localhost:4566"secretsmanager = "http://localhost:4566"serverlessrepo = "http://localhost:4566"servicediscovery = "http://localhost:4566"ses = "http://localhost:4566"sesv2 = "http://localhost:4566"sfn = "http://localhost:4566"sns = "http://localhost:4566"sqs = "http://localhost:4566"ssm = "http://localhost:4566"sts = "http://localhost:4566"swf = "http://localhost:4566"timestreamwrite = "http://localhost:4566"transcribe = "http://localhost:4566"transfer = "http://localhost:4566"waf = "http://localhost:4566"wafv2 = "http://localhost:4566"xray = "http://localhost:4566"}}

👾 provider.tf

よって,tflocalコマンドを使う場合の provider.tfはシンプルになる👌

provider"aws"{region = "ap-northeast-1"}

あとは tflocalコマンドで plan と apply を実行すれば OK❗️

$ tflocal plan
$ tflocal apply

S3 Backend サポート

tflocalコマンドは S3 Backend もサポートしていて,例えば以下の backend.tfのように S3 Backend で tfstate を管理している場合,自動的に同じ名前の Amazon S3 バケットを LocalStack にデプロイしてくれる.さらにステートロックのための Amazon DynamoDB テーブル tf-test-stateまで作ってくれる💡

👾 backend.tf

terraform{backend"s3"{region = "ap-northeast-1"bucket = "kakakakakku-sandbox-tfstates"key    = "terraform.tfstate"}}

そして localstack_providers_override.tfには S3 Backend のbackend設定まで追加される👌

provider"aws"{access_key                  = "test"secret_key                  = "test"skip_credentials_validation = trueskip_metadata_api_check     = trueregion = "ap-northeast-1"endpoints{acm = "http://localhost:4566"amplify = "http://localhost:4566"apigateway = "http://localhost:4566"apigatewayv2 = "http://localhost:4566"appautoscaling = "http://localhost:4566"appconfig = "http://localhost:4566"appflow = "http://localhost:4566"appsync = "http://localhost:4566"athena = "http://localhost:4566"autoscaling = "http://localhost:4566"backup = "http://localhost:4566"batch = "http://localhost:4566"ce = "http://localhost:4566"cloudformation = "http://localhost:4566"cloudfront = "http://localhost:4566"cloudsearch = "http://localhost:4566"cloudtrail = "http://localhost:4566"cloudwatch = "http://localhost:4566"codecommit = "http://localhost:4566"cognitoidentity = "http://localhost:4566"cognitoidp = "http://localhost:4566"configservice = "http://localhost:4566"docdb = "http://localhost:4566"dynamodb = "http://localhost:4566"ec2 = "http://localhost:4566"ecr = "http://localhost:4566"ecs = "http://localhost:4566"efs = "http://localhost:4566"eks = "http://localhost:4566"elasticache = "http://localhost:4566"elasticbeanstalk = "http://localhost:4566"elasticsearch = "http://localhost:4566"elb = "http://localhost:4566"elbv2 = "http://localhost:4566"emr = "http://localhost:4566"events = "http://localhost:4566"firehose = "http://localhost:4566"fis = "http://localhost:4566"glacier = "http://localhost:4566"glue = "http://localhost:4566"iam = "http://localhost:4566"iot = "http://localhost:4566"iotanalytics = "http://localhost:4566"iotevents = "http://localhost:4566"kafka = "http://localhost:4566"keyspaces = "http://localhost:4566"kinesis = "http://localhost:4566"kinesisanalytics = "http://localhost:4566"kinesisanalyticsv2 = "http://localhost:4566"kms = "http://localhost:4566"lakeformation = "http://localhost:4566"lambda = "http://localhost:4566"logs = "http://localhost:4566"mediaconvert = "http://localhost:4566"mediastore = "http://localhost:4566"mq = "http://localhost:4566"mwaa = "http://mwaa.localhost.localstack.cloud:4566"neptune = "http://localhost:4566"opensearch = "http://localhost:4566"organizations = "http://localhost:4566"pinpoint = "http://localhost:4566"pipes = "http://localhost:4566"qldb = "http://localhost:4566"ram = "http://localhost:4566"rds = "http://localhost:4566"redshift = "http://localhost:4566"redshiftdata = "http://localhost:4566"resourcegroups = "http://localhost:4566"resourcegroupstaggingapi = "http://localhost:4566"route53 = "http://localhost:4566"route53domains = "http://localhost:4566"route53resolver = "http://localhost:4566"s3 = "http://s3.localhost.localstack.cloud:4566"s3control = "http://localhost:4566"sagemaker = "http://localhost:4566"scheduler = "http://localhost:4566"secretsmanager = "http://localhost:4566"serverlessrepo = "http://localhost:4566"servicediscovery = "http://localhost:4566"ses = "http://localhost:4566"sesv2 = "http://localhost:4566"sfn = "http://localhost:4566"sns = "http://localhost:4566"sqs = "http://localhost:4566"ssm = "http://localhost:4566"sts = "http://localhost:4566"swf = "http://localhost:4566"timestreamwrite = "http://localhost:4566"transcribe = "http://localhost:4566"transfer = "http://localhost:4566"waf = "http://localhost:4566"wafv2 = "http://localhost:4566"xray = "http://localhost:4566"}}terraform{backend"s3"{access_key = "test"bucket = "kakakakakku-sandbox-tfstates"dynamodb_table = "tf-test-state"endpoints = {s3 = "http://s3.localhost.localstack.cloud:4566"iam = "http://localhost:4566"sso = "http://localhost:4566"sts = "http://localhost:4566"dynamodb = "http://localhost:4566"}key = "terraform.tfstate"region = "ap-northeast-1"secret_key = "test"skip_credentials_validation = trueskip_metadata_api_check = true}}

ちなみに Terraform v1.10 からは Amazon DynamoDB テーブルを使わずにステートロックを実現できるようになっていて(まだ Experimental),Terraform v1.11 から正式リリースになる予定のため,Terraform v1.11 だったら Amazon DynamoDB テーブルを作らず実行するように tflocalコマンドを改善できそうだな〜と考えている👌

Terraform v1.10.0 で導入された S3 Backend の use_lockfileオプションに関しては以下の記事にまとめてある❗️

kakakakakku.hatenablog.com

まとめ

Terraform から LocalStack にデプロイする場合,tflocalコマンドが便利なのでおすすめ〜 \( 'ω')/

DuckDB で CUR 2.0 データをクエリする

$
0
0

AWS Data Exportsでエクスポートした Cost and Usage Reports 2.0 (CUR 2.0)データを使って詳細な AWS コスト分析をする場合,よく聞く選択肢として Amazon Athena と Amazon QuickSight の組み合わせがある.個人的にクエリ部分で Amazon Athena を使わずにもっとカジュアルにコスト分析ができたら良いな〜と思っていて,最近 DuckDB を使っていてイイ感じに運用できている👌

AWS Well-Architected Cost & Usage Report Library

CUR 2.0 データからコスト分析をするときのクエリ例を探すときは「AWS Well-Architected Cost & Usage Report Library」が参考になる📝サービス別のコスト確認クエリやコスト最適化のための分析クエリなどたくさん載っている💰️(一部 CUR 2.0 に対応していなかったりするけど Legacy CUR を読み替えれば OK👌)

今回はサンプルとして AWS Well-Architected Cost & Usage Report Library に載っているクエリを2つ DuckDB で実行してみる❗️

catalog.workshops.aws

1. Amazon S3

まずは AWS Well-Architected Cost & Usage Report Library - Storageに載っている Amazon S3 のコスト分析クエリを試す.Amazon S3 の使用状況を詳細に分析できる👌

以下にクエリライブラリに載っている Legacy CURから変更した部分をまとめておく📝(あとブログ用に件数を絞るため LIMIT 10を追加した)

  • DATE_FORMAT()ではなく DuckDB でサポートされている STRFTIME()にした
  • プレースホルダ ${table_name}は Parquet 形式の CUR 2.0 データ 'kakakakakku-cur-00001.snappy.parquet'にした
  • プレースホルダ ${date_filter}STRFTIME()を使って 2025/01/01 ~ 2025/01/14の期間にした
SELECT
  bill_payer_account_id,
  line_item_usage_account_id,
  STRFTIME(line_item_usage_start_date, '%Y-%m-%d') AS day_line_item_usage_start_date,
  line_item_resource_id,
  line_item_operation,
  CASE--S3 Early DeleteWHEN line_item_usage_type LIKE'%EarlyDelete-ByteHrs'THEN'Early Delete Glacier'WHEN line_item_usage_type LIKE'%EarlyDelete%'THEN'Early Delete ' || SPLIT_PART(line_item_usage_type,'EarlyDelete-',2)
    --S3 RequestsWHEN line_item_usage_type LIKE'%Requests-INT%'THEN'Requests INT'WHEN (line_item_usage_type LIKE'%Requests-Tier1'OR line_item_usage_type LIKE'%Requests-Tier2') THEN'Requests Standard'WHEN (line_item_usage_type LIKE'%Requests-GLACIER%'OR line_item_usage_type LIKE'%Requests-Tier3'OR line_item_usage_type LIKE'%Requests-Tier5'OR line_item_usage_type LIKE'%Requests-Tier6') THEN'Requests Glacier'WHEN line_item_usage_type LIKE'%Requests-GDA%'THEN'Requests GDA'WHEN line_item_usage_type LIKE'%Requests-GIR%'THEN'Requests GIR'WHEN (line_item_usage_type LIKE'%Requests-Tier4'OR line_item_usage_type LIKE'%Requests-SIA%') THEN'Requests SIA'WHEN line_item_usage_type LIKE'%Requests-ZIA%'THEN'Requests ZIA'--S3 RetrievalWHEN (line_item_usage_type LIKE'%Retrieval-Bytes'AND line_item_operation = 'RestoreObject') THEN'Retrieval Glacier'WHEN (line_item_usage_type LIKE'%Retrieval-Bytes'AND line_item_operation = 'DeepArchiveRestoreObject') THEN'Retrieval GDA'WHEN line_item_usage_type LIKE'%Retrieval%'THEN'Retrieval ' || SPLIT_PART(line_item_usage_type,'Retrieval-',2)
    --S3 StorageWHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'StandardStorage') THEN'Storage Standard'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'StandardIAStorage') THEN'Storage SIA'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'StandardIASizeOverhead') THEN'Storage SIA-Overhead'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'OneZoneIAStorage') THEN'Storage ZIA'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'OneZoneIASizeOverhead') THEN'Storage ZIA-Overhead'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'GlacierInstantRetrievalStorage') THEN'Storage GIR'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'GlacierIRSizeOverhead') THEN'Storage GIR-Overhead'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'GlacierStorage') THEN'Storage Glacier'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'GlacierStagingStorage') THEN'Storage Glacier-Staging'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND (line_item_operation = 'GlacierObjectOverhead'or line_item_operation = 'GlacierS3ObjectOverhead')) THEN'Storage Glacier-Overhead'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'RestoreObject') THEN'Storage Glacier-Restored'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'DeepArchiveStorage') THEN'Storage GDA'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'DeepArchiveStagingStorage') THEN'Storage GDA-Staging'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND (line_item_operation = 'DeepArchiveObjectOverhead'or line_item_operation = 'DeepArchiveS3ObjectOverhead')) THEN'Storage GDA-Overhead'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'DeepArchiveRestoreObject') THEN'Storage GDA-Restored'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation = 'ReducedRedundancyStorage') THEN'Storage RRS'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation LIKE'IntelligentTieringAA%') THEN'Storage INT-AA'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND (line_item_operation = 'IntAAObjectOverhead'or line_item_operation = 'IntAAS3ObjectOverhead')) THEN'Storage INT-AA-Overhead'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation LIKE'IntelligentTieringDAA%') THEN'Storage INT-DAA'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND (line_item_operation = 'IntDAAObjectOverhead'or line_item_operation = 'IntDAAS3ObjectOverhead')) THEN'Storage INT-DAA-Overhead'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation LIKE'IntelligentTieringFA%') THEN'Storage INT-FA'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation LIKE'IntelligentTieringIA%') THEN'Storage INT-IA'WHEN (line_item_usage_type LIKE'%TimedStorage%'AND line_item_operation LIKE'IntelligentTieringAIA%') THEN'Storage INT-AIA'--Data TransferWHEN line_item_usage_type LIKE'%AWS-In-Bytes%'THEN'Data Transfer Region to Region (In)'WHEN line_item_usage_type LIKE'%AWS-In-ABytes%'THEN'Data Transfer Accelerated Region to Region (In)'WHEN line_item_usage_type LIKE'%AWS-Out-Bytes%'THEN'Data Transfer Region to Region (Out)'WHEN line_item_usage_type LIKE'%AWS-Out-ABytes%'THEN'Data Transfer Accelerated Region to Region (Out)'WHEN line_item_usage_type LIKE'%CloudFront-In-Bytes%'THEN'Data Transfer CloudFront (In)'WHEN line_item_usage_type LIKE'%CloudFront-Out-Bytes%'THEN'Data Transfer CloudFront (Out)'WHEN line_item_usage_type LIKE'%DataTransfer-Regional-Bytes%'THEN'Data Transfer Inter AZ'WHEN line_item_usage_type LIKE'%DataTransfer-In-Bytes%'THEN'Data Transfer Internet (In)'WHEN line_item_usage_type LIKE'%DataTransfer-Out-Bytes%'THEN'Data Transfer Internet (Out)'WHEN line_item_usage_type LIKE'%DataTransfer-In-ABytes%'THEN'Data Transfer Accelerated Internet (In)'WHEN line_item_usage_type LIKE'%DataTransfer-Out-ABytes%'THEN'Data Transfer Accelerated Internet (Out)'WHEN line_item_usage_type LIKE'%S3RTC-In-Bytes%'THEN'Data Transfer Replication Time Control (In)'WHEN line_item_usage_type LIKE'%S3RTC-Out-Bytes%'THEN'Data Transfer Replication Time Control (Out)'--S3 Fees & MiscWHEN line_item_usage_type LIKE'%Monitoring-Automation-INT'THEN'S3 INT Monitoring Fee'WHEN line_item_usage_type LIKE'%StorageAnalytics%'THEN'S3 Storage Analytics'WHEN line_item_usage_type LIKE'%BatchOperations-Jobs%'THEN'S3 Batch Operations-Jobs'WHEN line_item_usage_type LIKE'%BatchOperations-Objects%'THEN'S3 Batch Operations-Objects'WHEN line_item_usage_type LIKE'%TagStorage%'THEN'S3 Tag Storage'WHEN (line_item_usage_type LIKE'%Select-Returned%'OR line_item_usage_type LIKE'%Select-Scanned%') THEN'S3 Select'WHEN line_item_usage_type LIKE'%Inventory%'THEN'S3 Inventory'WHEN line_item_operation LIKE'%StorageLens%'THEN'Storage Lens'ELSE'Other ' || line_item_usage_type
  ENDas case_line_item_usage_type,
  SUM(CAST(line_item_usage_amount AS double)) AS sum_line_item_usage_amount,
  SUM(CAST(line_item_unblended_cost ASdecimal(16,8))) AS sum_line_item_unblended_cost
FROM'kakakakakku-cur-00001.snappy.parquet'WHERE
  STRFTIME(line_item_usage_start_date, '%Y-%m-%d') BETWEEN'2025-01-01'AND'2025-01-14'AND line_item_product_code = 'AmazonS3'AND line_item_line_item_type  in ('DiscountedUsage','Usage', 'SavingsPlanCoveredUsage')
  AND product_product_family != 'Data Transfer'GROUPBY
  bill_payer_account_id,
  line_item_usage_account_id,
  STRFTIME(line_item_usage_start_date, '%Y-%m-%d'),
  line_item_resource_id,
  line_item_operation,
  6--refers to case_line_item_usage_typeORDERBY
  sum_line_item_unblended_cost DESC
LIMIT 10;

実行すると以下の結果が取得できた \( 'ω')/

個人的な検証用 AWS アカウントだからほとんどコストは発生していないけど,リソース別・操作別に細かく確認できるのは便利❗️

┌───────────────────────┬────────────────────────────┬────────────────────────────────┬──────────────────────────────┬─────────────────────┬───────────────────────────┬────────────────────────────┬──────────────────────────────┐
│ bill_payer_account_id │ line_item_usage_account_id │ day_line_item_usage_start_date │    line_item_resource_id     │ line_item_operation │ case_line_item_usage_type │ sum_line_item_usage_amount │ sum_line_item_unblended_cost │
│        varchar        │          varchar           │            varchar             │           varchar            │       varchar       │          varchar          │           double           │        decimal(38,8)         │
├───────────────────────┼────────────────────────────┼────────────────────────────────┼──────────────────────────────┼─────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────┤
│ 000000000000          │ 000000000000               │ 2025-01-01                     │                              │ ListAllMyBuckets    │ Requests Standard         │                       41.0 │                   0.00020500 │
│ 000000000000          │ 000000000000               │ 2025-01-12                     │ kakakakakku-sandbox-tfstates │ ListBucket          │ Requests Standard         │                       39.0 │                   0.00018330 │
│ 000000000000          │ 000000000000               │ 2025-01-01                     │ kakakakakku-s3-presign       │ ListBucket          │ Requests Standard         │                       28.0 │                   0.00013160 │
│ 000000000000          │ 000000000000               │ 2025-01-11                     │ kakakakakku-sandbox-tfstates │ ListBucket          │ Requests Standard         │                       27.0 │                   0.00012690 │
│ 000000000000          │ 000000000000               │ 2025-01-01                     │ kakakakakku-s3-presign       │ PutObject           │ Requests Standard         │                       25.0 │                   0.00011750 │
│ 000000000000          │ 000000000000               │ 2025-01-12                     │ kakakakakku-sandbox-tfstates │ PutObject           │ Requests Standard         │                       18.0 │                   0.00008460 │
│ 000000000000          │ 000000000000               │ 2025-01-01                     │ kakakakakku-cur              │ ListBucket          │ Requests Standard         │                       16.0 │                   0.00008000 │
│ 000000000000          │ 000000000000               │ 2025-01-12                     │ kakakakakku-sandbox-tfstates │ ListBucketVersions  │ Requests Standard         │                       14.0 │                   0.00006580 │
│ 000000000000          │ 000000000000               │ 2025-01-01                     │ kakakakakku-s3-presign       │ WriteBucketPolicy   │ Requests Standard         │                       10.0 │                   0.00004700 │
│ 000000000000          │ 000000000000               │ 2025-01-03                     │ kakakakakku-cur              │ ListBucket          │ Requests Standard         │                        8.0 │                   0.00004000 │
├───────────────────────┴────────────────────────────┴────────────────────────────────┴──────────────────────────────┴─────────────────────┴───────────────────────────┴────────────────────────────┴──────────────────────────────┤
│ 10 rows                                                                                                                                                                                                                8 columns │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

2. Amazon CloudWatch

次に AWS Well-Architected Cost & Usage Report Library - Management & Governanceに載っている Amazon CloudWatch のコスト分析クエリを試す.Amazon CloudWatch の使用状況を詳細に分析できる👌

以下にクエリライブラリに載っている Legacy CURから変更した部分をまとめておく📝(あとブログ用に件数を絞るため LIMIT 10を追加した)

  • DATE_FORMAT()ではなく DuckDB でサポートされている STRFTIME()にした
  • プレースホルダ ${table_name}は Parquet 形式の CUR 2.0 データ 'kakakakakku-cur-00001.snappy.parquet'にした
  • プレースホルダ ${date_filter}STRFTIME()を使って 2025/01/01 ~ 2025/01/14の期間にした
  • カラム product_product_nameは CUR 2.0 のカラム line_item_product_codeにした
SELECT 
  bill_payer_account_id,
  line_item_usage_account_id,
  STRFTIME(line_item_usage_start_date, '%Y-%m-%d') AS day_line_item_usage_start_date,
  CASEWHEN line_item_usage_type LIKE'%%Requests%%'THEN'Requests'WHEN line_item_usage_type LIKE'%%DataProcessing-Bytes%%'THEN'DataProcessing'WHEN line_item_usage_type LIKE'%%TimedStorage-ByteHrs%%'THEN'Storage'WHEN line_item_usage_type LIKE'%%DataScanned-Bytes%%'THEN'DataScanned'WHEN line_item_usage_type LIKE'%%AlarmMonitorUsage%%'THEN'AlarmMonitors'WHEN line_item_usage_type LIKE'%%DashboardsUsageHour%%'THEN'Dashboards'WHEN line_item_usage_type LIKE'%%MetricMonitorUsage%%'THEN'MetricMonitor'WHEN line_item_usage_type LIKE'%%VendedLog-Bytes%%'THEN'VendedLogs'WHEN line_item_usage_type LIKE'%%GMD-Metrics%%'THEN'GetMetricData'ELSE'Others'ENDAS line_item_usage_type,
  -- if uncommenting, also uncomment one other occurrence of line_item_resource_id in GROUP BY-- SPLIT_PART(line_item_resource_id,':',7) as ResourceID, 
  line_item_operation,
  SUM(CAST(line_item_usage_amount AS DOUBLE)) AS sum_line_item_usage_amount,
  SUM(CAST(line_item_unblended_cost ASDECIMAL(16,8))) AS sum_line_item_unblended_cost 
FROM'kakakakakku-cur-00001.snappy.parquet'WHERE 
  STRFTIME(line_item_usage_start_date, '%Y-%m-%d') BETWEEN'2025-01-01'AND'2025-01-14'AND line_item_product_code = 'AmazonCloudWatch'AND line_item_line_item_type  IN ('DiscountedUsage', 'Usage', 'SavingsPlanCoveredUsage')
GROUPBY
  bill_payer_account_id, 
  line_item_usage_account_id,
  STRFTIME(line_item_usage_start_date, '%Y-%m-%d'),
  line_item_usage_type,
  -- line_item_resource_id,
  line_item_operation
ORDERBY
sum_line_item_unblended_cost DESC
LIMIT 10;

実行すると以下の結果が取得できた \( 'ω')/

個人的な検証用 AWS アカウントだからほとんどコストは発生していないけど,使用タイプ別に細かく確認できるのは便利❗️

ちなみに結果を見て「不要な Amazon CloudWatch Dashboard」が残ってるじゃん〜💦 と気付いてすぐに削除できたのは良かった👌

┌───────────────────────┬────────────────────────────┬────────────────────────────────┬──────────────────────┬───────────────────────┬────────────────────────────┬──────────────────────────────┐
│ bill_payer_account_id │ line_item_usage_account_id │ day_line_item_usage_start_date │ line_item_usage_type │  line_item_operation  │ sum_line_item_usage_amount │ sum_line_item_unblended_cost │
│        varchar        │          varchar           │            varchar             │       varchar        │        varchar        │           double           │        decimal(38,8)         │
├───────────────────────┼────────────────────────────┼────────────────────────────────┼──────────────────────┼───────────────────────┼────────────────────────────┼──────────────────────────────┤
│ 000000000000          │ 000000000000               │ 2025-01-05                     │ Dashboards           │ DashboardHour         │                0.032258064 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-05                     │ AlarmMonitors        │ Unknown               │                0.032258064 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-10                     │ AlarmMonitors        │ Unknown               │                0.032258064 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-01                     │ Storage              │ HourlyStorageMetering │      8.702999999999998e-07 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-09                     │ Storage              │ HourlyStorageMetering │      8.702999999999998e-07 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-12                     │ Storage              │ HourlyStorageMetering │      8.702999999999998e-07 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-06                     │ Storage              │ HourlyStorageMetering │                 9.5485e-06 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-09                     │ Storage              │ HourlyStorageMetering │                 9.5485e-06 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-09                     │ Dashboards           │ DashboardHour         │                0.032258064 │                   0.00000000 │
│ 000000000000          │ 000000000000               │ 2025-01-14                     │ Dashboards           │ DashboardHour         │                0.032258064 │                   0.00000000 │
├───────────────────────┴────────────────────────────┴────────────────────────────────┴──────────────────────┴───────────────────────┴────────────────────────────┴──────────────────────────────┤
│ 10 rows                                                                                                                                                                              7 columns │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

AWS CloudShell に DuckDB をインストールする

今回は Parquet 形式の CUR 2.0 データを Amazon S3 からローカル環境に落としてから DuckDB で読み込んだけど,Amazon S3 を直接読み込む場合,AWS CloudShell に DuckDB をインストールすると便利なアドホッククエリ実行環境になるのでおすすめ❗️

duckdb.org

$ curl --fail--location--progress-bar--output duckdb_cli-linux-amd64.zip https://github.com/duckdb/duckdb/releases/download/v1.1.3/duckdb_cli-linux-amd64.zip && unzip duckdb_cli-linux-amd64.zip

$ ./duckdb

D CREATE SECRET (      TYPE S3,      PROVIDER CREDENTIAL_CHAIN);
┌─────────┐
│ Success │
│ boolean │
├─────────┤
│ true│
└─────────┘

D .maxrows 200

D DESCRIBE SELECT * FROM read_parquet('s3://kakakakakku-cur/cur/kakakakakku-cur/data/BILLING_PERIOD=2025-01/kakakakakku-cur-00001.snappy.parquet');
┌──────────────────────────────────────────────────────────────────┬───────────────────────┬─────────┬─────────┬─────────┬─────────┐
│                           column_name                            │      column_type      │  null   │   key   │ default │  extra  │
│                             varchar                              │        varchar        │ varchar │ varchar │ varchar │ varchar │
├──────────────────────────────────────────────────────────────────┼───────────────────────┼─────────┼─────────┼─────────┼─────────┤
│ bill_bill_type                                                   │ VARCHAR               │ YES     │         │         │         │
│ bill_billing_entity                                              │ VARCHAR               │ YES     │         │         │         │
│ bill_billing_period_end_date                                     │ TIMESTAMP             │ YES     │         │         │         │
│ bill_billing_period_start_date                                   │ TIMESTAMP             │ YES     │         │         │         │
│ bill_invoice_id                                                  │ VARCHAR               │ YES     │         │         │         │
│ bill_invoicing_entity                                            │ VARCHAR               │ YES     │         │         │         │
│ bill_payer_account_id                                            │ VARCHAR               │ YES     │         │         │         │
│ bill_payer_account_name                                          │ VARCHAR               │ YES     │         │         │         │
│ cost_category                                                    │ MAP(VARCHAR, VARCHAR)│ YES     │         │         │         │
│ discount                                                         │ MAP(VARCHAR, DOUBLE)│ YES     │         │         │         │
│ discount_bundled_discount                                        │ DOUBLE                │ YES     │         │         │         │
│ discount_total_discount                                          │ DOUBLE                │ YES     │         │         │         │
│ identity_line_item_id                                            │ VARCHAR               │ YES     │         │         │         │
│ identity_time_interval                                           │ VARCHAR               │ YES     │         │         │         │
│ line_item_availability_zone                                      │ VARCHAR               │ YES     │         │         │         │
│ line_item_blended_cost                                           │ DOUBLE                │ YES     │         │         │         │
│ line_item_blended_rate                                           │ VARCHAR               │ YES     │         │         │         │
│ line_item_currency_code                                          │ VARCHAR               │ YES     │         │         │         │
│ line_item_legal_entity                                           │ VARCHAR               │ YES     │         │         │         │
│ line_item_line_item_description                                  │ VARCHAR               │ YES     │         │         │         │
│ line_item_line_item_type                                         │ VARCHAR               │ YES     │         │         │         │
│ line_item_net_unblended_cost                                     │ DOUBLE                │ YES     │         │         │         │
│ line_item_net_unblended_rate                                     │ VARCHAR               │ YES     │         │         │         │
│ line_item_normalization_factor                                   │ DOUBLE                │ YES     │         │         │         │
│ line_item_normalized_usage_amount                                │ DOUBLE                │ YES     │         │         │         │
│ line_item_operation                                              │ VARCHAR               │ YES     │         │         │         │
│ line_item_product_code                                           │ VARCHAR               │ YES     │         │         │         │
│ line_item_tax_type                                               │ VARCHAR               │ YES     │         │         │         │
│ line_item_unblended_cost                                         │ DOUBLE                │ YES     │         │         │         │
│ line_item_unblended_rate                                         │ VARCHAR               │ YES     │         │         │         │
│ line_item_usage_account_id                                       │ VARCHAR               │ YES     │         │         │         │
│ line_item_usage_account_name                                     │ VARCHAR               │ YES     │         │         │         │
│ line_item_usage_amount                                           │ DOUBLE                │ YES     │         │         │         │
│ line_item_usage_end_date                                         │ TIMESTAMP             │ YES     │         │         │         │
│ line_item_usage_start_date                                       │ TIMESTAMP             │ YES     │         │         │         │
│ line_item_usage_type                                             │ VARCHAR               │ YES     │         │         │         │
│ pricing_currency                                                 │ VARCHAR               │ YES     │         │         │         │
│ pricing_lease_contract_length                                    │ VARCHAR               │ YES     │         │         │         │
│ pricing_offering_class                                           │ VARCHAR               │ YES     │         │         │         │
│ pricing_public_on_demand_cost                                    │ DOUBLE                │ YES     │         │         │         │
│ pricing_public_on_demand_rate                                    │ VARCHAR               │ YES     │         │         │         │
│ pricing_purchase_option                                          │ VARCHAR               │ YES     │         │         │         │
│ pricing_rate_code                                                │ VARCHAR               │ YES     │         │         │         │
│ pricing_rate_id                                                  │ VARCHAR               │ YES     │         │         │         │
│ pricing_term                                                     │ VARCHAR               │ YES     │         │         │         │
│ pricing_unit                                                     │ VARCHAR               │ YES     │         │         │         │
│ product                                                          │ MAP(VARCHAR, VARCHAR)│ YES     │         │         │         │
│ product_comment                                                  │ VARCHAR               │ YES     │         │         │         │
│ product_fee_code                                                 │ VARCHAR               │ YES     │         │         │         │
│ product_fee_description                                          │ VARCHAR               │ YES     │         │         │         │
│ product_from_location                                            │ VARCHAR               │ YES     │         │         │         │
│ product_from_location_type                                       │ VARCHAR               │ YES     │         │         │         │
│ product_from_region_code                                         │ VARCHAR               │ YES     │         │         │         │
│ product_instance_family                                          │ VARCHAR               │ YES     │         │         │         │
│ product_instance_type                                            │ VARCHAR               │ YES     │         │         │         │
│ product_instancesku                                              │ VARCHAR               │ YES     │         │         │         │
│ product_location                                                 │ VARCHAR               │ YES     │         │         │         │
│ product_location_type                                            │ VARCHAR               │ YES     │         │         │         │
│ product_operation                                                │ VARCHAR               │ YES     │         │         │         │
│ product_pricing_unit                                             │ VARCHAR               │ YES     │         │         │         │
│ product_product_family                                           │ VARCHAR               │ YES     │         │         │         │
│ product_region_code                                              │ VARCHAR               │ YES     │         │         │         │
│ product_servicecode                                              │ VARCHAR               │ YES     │         │         │         │
│ product_sku                                                      │ VARCHAR               │ YES     │         │         │         │
│ product_to_location                                              │ VARCHAR               │ YES     │         │         │         │
│ product_to_location_type                                         │ VARCHAR               │ YES     │         │         │         │
│ product_to_region_code                                           │ VARCHAR               │ YES     │         │         │         │
│ product_usagetype                                                │ VARCHAR               │ YES     │         │         │         │
│ reservation_amortized_upfront_cost_for_usage                     │ DOUBLE                │ YES     │         │         │         │
│ reservation_amortized_upfront_fee_for_billing_period             │ DOUBLE                │ YES     │         │         │         │
│ reservation_availability_zone                                    │ VARCHAR               │ YES     │         │         │         │
│ reservation_effective_cost                                       │ DOUBLE                │ YES     │         │         │         │
│ reservation_end_time                                             │ VARCHAR               │ YES     │         │         │         │
│ reservation_modification_status                                  │ VARCHAR               │ YES     │         │         │         │
│ reservation_net_amortized_upfront_cost_for_usage                 │ DOUBLE                │ YES     │         │         │         │
│ reservation_net_amortized_upfront_fee_for_billing_period         │ DOUBLE                │ YES     │         │         │         │
│ reservation_net_effective_cost                                   │ DOUBLE                │ YES     │         │         │         │
│ reservation_net_recurring_fee_for_usage                          │ DOUBLE                │ YES     │         │         │         │
│ reservation_net_unused_amortized_upfront_fee_for_billing_period  │ DOUBLE                │ YES     │         │         │         │
│ reservation_net_unused_recurring_fee                             │ DOUBLE                │ YES     │         │         │         │
│ reservation_net_upfront_value                                    │ DOUBLE                │ YES     │         │         │         │
│ reservation_normalized_units_per_reservation                     │ VARCHAR               │ YES     │         │         │         │
│ reservation_number_of_reservations                               │ VARCHAR               │ YES     │         │         │         │
│ reservation_recurring_fee_for_usage                              │ DOUBLE                │ YES     │         │         │         │
│ reservation_reservation_a_r_n                                    │ VARCHAR               │ YES     │         │         │         │
│ reservation_start_time                                           │ VARCHAR               │ YES     │         │         │         │
│ reservation_subscription_id                                      │ VARCHAR               │ YES     │         │         │         │
│ reservation_total_reserved_normalized_units                      │ VARCHAR               │ YES     │         │         │         │
│ reservation_total_reserved_units                                 │ VARCHAR               │ YES     │         │         │         │
│ reservation_units_per_reservation                                │ VARCHAR               │ YES     │         │         │         │
│ reservation_unused_amortized_upfront_fee_for_billing_period      │ DOUBLE                │ YES     │         │         │         │
│ reservation_unused_normalized_unit_quantity                      │ DOUBLE                │ YES     │         │         │         │
│ reservation_unused_quantity                                      │ DOUBLE                │ YES     │         │         │         │
│ reservation_unused_recurring_fee                                 │ DOUBLE                │ YES     │         │         │         │
│ reservation_upfront_value                                        │ DOUBLE                │ YES     │         │         │         │
│ resource_tags                                                    │ MAP(VARCHAR, VARCHAR)│ YES     │         │         │         │
│ savings_plan_amortized_upfront_commitment_for_billing_period     │ DOUBLE                │ YES     │         │         │         │
│ savings_plan_end_time                                            │ VARCHAR               │ YES     │         │         │         │
│ savings_plan_instance_type_family                                │ VARCHAR               │ YES     │         │         │         │
│ savings_plan_net_amortized_upfront_commitment_for_billing_period │ DOUBLE                │ YES     │         │         │         │
│ savings_plan_net_recurring_commitment_for_billing_period         │ DOUBLE                │ YES     │         │         │         │
│ savings_plan_net_savings_plan_effective_cost                     │ DOUBLE                │ YES     │         │         │         │
│ savings_plan_offering_type                                       │ VARCHAR               │ YES     │         │         │         │
│ savings_plan_payment_option                                      │ VARCHAR               │ YES     │         │         │         │
│ savings_plan_purchase_term                                       │ VARCHAR               │ YES     │         │         │         │
│ savings_plan_recurring_commitment_for_billing_period             │ DOUBLE                │ YES     │         │         │         │
│ savings_plan_region                                              │ VARCHAR               │ YES     │         │         │         │
│ savings_plan_savings_plan_a_r_n                                  │ VARCHAR               │ YES     │         │         │         │
│ savings_plan_savings_plan_effective_cost                         │ DOUBLE                │ YES     │         │         │         │
│ savings_plan_savings_plan_rate                                   │ DOUBLE                │ YES     │         │         │         │
│ savings_plan_start_time                                          │ VARCHAR               │ YES     │         │         │         │
│ savings_plan_total_commitment_to_date                            │ DOUBLE                │ YES     │         │         │         │
│ savings_plan_used_commitment                                     │ DOUBLE                │ YES     │         │         │         │
│ line_item_resource_id                                            │ VARCHAR               │ YES     │         │         │         │
│ BILLING_PERIOD                                                   │ VARCHAR               │ YES     │         │         │         │
├──────────────────────────────────────────────────────────────────┴───────────────────────┴─────────┴─────────┴─────────┴─────────┤
│ 115 rows                                                                                                               6 columns │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

まとめ

CUR 2.0 の分析に DuckDB を活用しよう❗️

関連情報

AWS CDK で Cost and Usage Reports 2.0 (CUR 2.0) エクスポートを設定する📝

kakakakakku.hatenablog.com

CUR 2.0 データのカラムの意味を確認する場合はドキュメント参照📝

docs.aws.amazon.com


3年振りにアップデート📊 Redash v25.1.0 を試せる「Redash ハンズオン資料」

$
0
0

Redash v25.1.0 📊

2025年1月9日に Redash の正式バージョンとしては「3年振り」となる Redash v25.1.0がリリースされた❗️内部的にはコツコツとアップデートされていたようだけど(Release Notes 参照),GitHub Releases では2021年11月にリリースされた Redash v10.1.0 から Redash v25.1.0 へのバージョンアップになっている👌

github.com

redash-hands-on 📊

ということで,2017年に公開してから継続的にアップデートをしている「Redash ハンズオン資料 (redash-hands-on)」を Redash v25.1.0 に対応させた🎉

redash-hands-on は Redash の正式バージョンにあわせてアップデートをしているため,直近数年は redash-hands-on のメンテナンスをやめたのではなく,新しく Redash の正式バージョンが出るのを待っていた感じだった \( 'ω')/

github.com

変更点 📊

主な変更点を以下にまとめた📝 正直大きな変化はなくて,今まで通り Redash の「知っておくと便利な機能」を一通り試せる内容になっている❗️

  • 全般
    • Redash v25.1.0 サポート
    • スクリーンショットを更新
    • UI 変更に合わせて手順を見直し
    • その他細かな注釈を追加
  • 環境
    • 最新の Docker Compose に沿って YAML ファイル名・コマンドを更新
    • worldデータベースを MySQL 8.4 にバージョンアップ

まとめ

今から新しく Redash を導入するっていう機会は減っているかもしれないけど,昔から Redash を使い続けている現場はあるし,Redash の機能を試したいな〜っていうときに redash-hands-onを活用してもらえればと👌

Happy querying :)

ちなみに最近は Zenn Book + GitHub Codespacesという組み合わせてコンテンツを作っていて,特に GitHub Codespaces でローカル環境を汚さずに試せるのが最高なので,redash-hands-on も Zenn Book + GitHub Codespaces に移行しても良いかな〜と考え中😀

zenn.dev

zenn.dev

関連記事

kakakakakku.hatenablog.comkakakakakku.hatenablog.comkakakakakku.hatenablog.comkakakakakku.hatenablog.comkakakakakku.hatenablog.comkakakakakku.hatenablog.comkakakakakku.hatenablog.com

AWS CDK で Cognito の Managed Login を設定する

$
0
0

2025年1月25日にリリースされた AWS CDK v2.177.0で Amazon Cognito の Managed Login(マネージドログイン)が L2 コンストラクト UserPoolDomainでサポートされていた👌リリースノートには cognito: managed loginとしか書かれてなく,イメージを掴むために実際に試してみることにした❗️

github.com

👾 cognito-managed-login.ts(デフォルト)

AWS CDK v2.177.0 から UserPoolDomainmanagedLoginVersionを設定できるようになっていた💡値としては 1: Hosted UI (Classic)もしくは 2: Managed Loginを設定することができて,AWS CDK だと ManagedLoginVersion enum を使う.

docs.aws.amazon.com

import{
    Stack,
    StackProps,
    aws_cognito,
}from'aws-cdk-lib'import{ Construct }from'constructs'exportclass CognitoManagedLoginStack extends Stack {constructor(scope:Construct, id:string, props?:StackProps) {super(scope, id, props)

        const userPool = new aws_cognito.UserPool(this, 'UserPool', {userPoolName: 'sandbox',
        })

        userPool.addDomain('UserPoolDomain', {cognitoDomain: {domainPrefix: 'sandbox-managed-login',
            },
            managedLoginVersion: aws_cognito.ManagedLoginVersion.NEWER_MANAGED_LOGIN,
        })

        const userPoolClient = userPool.addClient('UserPoolClient', {userPoolClientName: 'sandbox-managed-login',
            generateSecret: false,
        })

        new aws_cognito.CfnManagedLoginBranding(this, 'ManagedLoginBranding', {userPoolId: userPool.userPoolId,
            clientId: userPoolClient.userPoolClientId,
            useCognitoProvidedValues: true,
        })
    }}

デプロイすると Managed Login(マネージドログイン)のデフォルトスタイルでログイン画面を表示できた💡

ログインページ(デフォルト)

👾 cognito-managed-login.ts(カスタマイズ)

Managed Login(マネージドログイン)でスタイルをカスタマイズする場合は L1 コンストラクト CfnManagedLoginBrandingを使う必要がある.将来的にここも L2 になると良さそう👌

docs.aws.amazon.com

またカスタマイズ内容を assetssettingsに設定するときは,ドキュメントにも書いてある通り,一度マネジメントコンソールでスタイルを設定してから aws cognito-idp describe-managed-login-branding-by-clientコマンドで出力して転記するのが良いと思う.今回は最低限のカスタマイズとして「ダークモード」にしてみた.

import{
    Stack,
    StackProps,
    aws_cognito,
}from'aws-cdk-lib'import{ Construct }from'constructs'exportclass CognitoManagedLoginStack extends Stack {constructor(scope:Construct, id:string, props?:StackProps) {super(scope, id, props)

        const userPool = new aws_cognito.UserPool(this, 'UserPool', {userPoolName: 'sandbox',
        })

        userPool.addDomain('UserPoolDomain', {cognitoDomain: {domainPrefix: 'sandbox-managed-login',
            },
            managedLoginVersion: aws_cognito.ManagedLoginVersion.NEWER_MANAGED_LOGIN,
        })

        const userPoolClient = userPool.addClient('UserPoolClient', {userPoolClientName: 'sandbox-managed-login',
            generateSecret: false,
        })

        new aws_cognito.CfnManagedLoginBranding(this, 'ManagedLoginBranding', {userPoolId: userPool.userPoolId,
            clientId: userPoolClient.userPoolClientId,
            returnMergedResources: true,
            settings: {'categories': {'global': {'colorSchemeMode': 'DARK'}}}})
    }}

デプロイすると Managed Login(マネージドログイン)のカスタマイズしたスタイルでログイン画面を表示できた💡

ログインページ(カスタマイズ)

GitHub の CODEOWNERS でプルリクエストのレビュアーを自動アサインしよう

$
0
0

GitHub で CODEOWNERSファイルを使うとコードに対するオーナーシップを設定することができて,プルリクエストを作ると自動的にレビュアーにアサインされるという仕組みを実現できる👌ドラフト状態のプルリクエストだとレビュアーはアサインされないようにもなっていてイイ感じ \( 'ω')/

docs.github.com

"Looks Good to Me"

localstack/CODEOWNERSterraform/CODEOWNERSもあって,GitHub の CODEOWNERSファイルという機能は前から知っていたけど,書籍 "Looks Good to Me"を読んでいるときに Chapter.5「The advantages of automation」「レビュアーのアサインを自動化する」というトピックが紹介されていて,仕事でも使えそうだな〜と感じた💡

モノレポに CODEOWNERSファイルを導入する

検証としてモノレポに CODEOWNERSファイルを導入してみる.まず以下のような構成 (Yarn Workspaces) を作っておく.

$ tree -L2 .
.
├── CODEOWNERS
├── README.md
└── packages
    ├── app1
    ├── app2
    └── app3

そして CODEOWNERSファイルを準備する.あくまでサンプルとして /packages/app1/packages/app2にはコードオーナー(リードエンジニアなど)を設定して,/packages/app3には GitHub Organization Team を設定しておく📝 またドキュメントには CODEOWNERSファイル自体にもコードオーナーを設定しておくと良いと書いてあって,設定してある.

/CODEOWNERS @kakakakakku

/packages/app1 @kakakakakku
/packages/app2 @kakakakakku-sandbox
/packages/app3 @kakakakakku-corp/app3-developers

GitHub Organization Team を使えばレビュアーにアサインするアルゴリズムを設定できて,バランス良くコードレビューを分担できる👌さらに GitHub アカウントで Busyステータスを設定してくとレビュアーの候補から除外されたりもして,ある程度の規模の開発組織だったら GitHub Organization Team を積極的に使うと良さそう.

docs.github.com

app2 の変更を含んだプルリクエストを作る

app3 の変更を含んだプルリクエストを作る

app1,2,3 の変更を含んだプルリクエストを作る

イイ感じ \( 'ω')/

コードレビュー時に GitHub の Saved replies に保存したコメントプレフィックスを使う

$
0
0

書籍 "Looks Good to Me"を読んでいたら,Chapter.6「Composing effective code review comments」にコメントプレフィックスのトピックがあっておもしろかった❗️大きく3種類紹介されていた.

  • MoSCoW Comments
  • Conventional Comments
  • Comment Signals

僕自身はプルリクエストのレビューをするときによく (対応必須ではないです!) / [IMO] / [nits] / [typo]などのテキストをコメントに含めるようにしていて,本書で紹介されていたコメントプレフィックスとニュアンスは同じなので,今後は Conventional Commentsなどよく知られたものに合わせようかな〜なんて考えながら本書を読んでいた💬

MoSCoW Comments

MoSCoW Commentsは Musts / Shoulds / Coulds / Woulds をコメントプレフィックスとして使う💡

en.wikipedia.org

Conventional Comments

Conventional Comments は有名な Conventional Commitsの「コメント版」って感じで issue (non-blocking):suggestion:note:などすぐに活用できそうなコメントプレフィックスとフォーマットが定義されている👌詳しくはウェブサイトを参照 \( 'ω')/

conventionalcomments.org

Comment Signals

そして,書籍 "Looks Good to Me"の著者が実際に MoSCoW Comments と Conventional Comments を使ってみたところ課題を感じて,最終的に独自の Comment Signalsにたどり着いたという話も載っていた.Comment Signals は大きく5種類のコメントプレフィックスから構成されていて,確かに使いやすそうだな〜という印象だった😀

  • needs change:
  • needs rework:
  • align:
  • levelup:
  • nitpick:

GitHub の Saved replies 機能

例えば Conventional Comments の note:だったら特に負担に感じることもなく入力できるけど,suggestion (non-blocking):ってなると入力するのも少し大変だな〜と思う.そんなときは GitHub の Saved replies機能が便利❗️コメントプレフィックスに限らず,よく使うコメントスニペットを保存しておくことができる.

docs.github.com

サンプルとして以下のように4種類 Saved repliesを保存しておく✔️

nitpick:
note:
suggestion (non-blocking):
question:

そしてコメントを書くときに Saved repliesボタンもしくは Slash commandsから保存したコメントスニペットを呼び出せる❗️

まとめ

コメントプレフィックスと GitHub の Saved replies 機能を組み合わせは便利だ〜 \( 'ω')/

GitHub Codespaces のストレージ使用率 90% !? DuckDB で GitHub Usage Report を分析する

$
0
0

最近 GitHub から以下のようなメールが届いていた✉️

[GitHub] You've used 75% of included services for the kakakakakku account
[GitHub] You've used 90% of included services for the kakakakakku account

GitHub Codespaces では毎月「コア時間」「ストレージ」に無料利用枠が設定されていて,Settings>Billing and plans (Spending limit)「Email alerts」の設定をしておくと,75% / 90% / 100% でメールが届く.

docs.github.com

実際に Settings>Billing and plans (Plans and usage)を確認したところ You've used 90% of included services for GitHub Codespaces storage.というアラートが出ていて,ストレージ(GitHub Pro だと 20 GB)で 90% を超えていて,GitHub 画面で確認できる内訳としては以下となっていた📝

Storage: 8.99 GB
Prebuild Storage: 9.16 GB

Zenn Book と GitHub Codespaces

現在 Zenn Book で「LocalStack 実践入門」というワークショップを2つ公開していて(AWS アプリケーション開発ワークショップAWS サーバレスパターン開発ワークショップ),ワークショップ環境として GitHub Codespaces を採用している💡さらに GitHub Codespaces の起動時間を短くするために GitHub Codespaces prebuilds(プレビルド)という機能も採用している❗️

kakakakakku.hatenablog.com

よって内訳として,ワークショップの動作確認をしたときに Storageが消費されて,プレビルドを保管していると Prebuild Storageが消費されるということは予想できるものの,それ以上の情報はなく,もっと詳細に分析したいな〜と感じた.

GitHub Usage Report

そんなときには GitHub の「Usage Report」を活用できる❗️

Usage Report(CSV ファイル)には GitHub Actions, GitHub Packages, GitHub Codespaces の利用状況がまとまっている.期間としては過去7日間 / 過去30日間 / 過去90日間 / 過去180日間から選べる📅

docs.github.com

GitHub Usage Report と DuckDB

Usage Report の分析に DuckDBが使える🦆

データ構造

⚫◗ DESCRIBE SELECT *
    FROM'4b3a5d14_2025-02-06_180.csv';
┌────────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│    column_name     │ column_type │  null│   key   │ default│  extra  │
│      varcharvarcharvarcharvarcharvarcharvarchar│
├────────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ DateDATE│ YES     │         │         │         │
│ Product            │ VARCHAR│ YES     │         │         │         │
│ SKU                │ VARCHAR│ YES     │         │         │         │
│ Quantity           │ DOUBLE      │ YES     │         │         │         │
│ Unit TypeVARCHAR│ YES     │         │         │         │
│ Price Per Unit ($) │ DOUBLE      │ YES     │         │         │         │
│ Multiplier         │ DOUBLE      │ YES     │         │         │         │
│ Owner              │ VARCHAR│ YES     │         │         │         │
│ Repository Slug    │ VARCHAR│ YES     │         │         │         │
│ Username           │ VARCHAR│ YES     │         │         │         │
│ Actions Workflow   │ VARCHAR│ YES     │         │         │         │
│ Notes              │ VARCHAR│ YES     │         │         │         │
├────────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┤
│ 12rows6 columns │
└──────────────────────────────────────────────────────────────────────────┘

Product / SKU カラム別の件数(2025年1月)

1ヶ月間の区切りは請求月の開始日に紐付いていて,僕自身の環境だと「毎月6日〜翌月5日まで」となる.

⚫◗ SELECT Product, SKU, COUNT(*) ASCOUNTFROM'4b3a5d14_2025-02-06_180.csv'WHEREDateBETWEEN'2025-01-06'AND'2025-02-05'GROUPBY Product, SKU
    ORDERBY Product;
┌────────────────────┬──────────────────┬───────┐
│      Product       │       SKU        │ COUNT│
│      varcharvarchar│ int64 │
├────────────────────┼──────────────────┼───────┤
│ Actions            │ Compute - UBUNTU │     1│
│ Codespaces - Linux │ Compute - 2 core │     2│
│ Codespaces - Linux │ Storage          │    57│
│ Codespaces - Linux │ Prebuild storage │    62│
│ Shared Storage     │ Shared Storage   │     2│
└────────────────────┴──────────────────┴───────┘

GitHub Codespaces の利用状況(2025年1月)

GitHub 画面をキャプチャしたタイミングと Usage Report をダウンロードしたタイミングが微妙に違うけど,ほとんど同じ値になっていることを確認できた👌

⚫◗ SELECT SKU, SUM(Quantity) ASSUMFROM'4b3a5d14_2025-02-06_180.csv'WHERE
        Product = 'Codespaces - Linux'ANDDateBETWEEN'2025-01-06'AND'2025-02-05'GROUPBY SKU;
┌──────────────────┬────────────────────┐
│       SKU        │        SUM│
│     varchar│       double       │
├──────────────────┼────────────────────┤
│ Compute - 2 core │ 1.2688000000000001│
│ Storage          │             8.9764│
│ Prebuild storage │   9.13889999999999│
└──────────────────┴────────────────────┘

GitHub Codespaces prebuilds(プレビルド)のリポジトリ別の利用状況(2025年1月)

Repository Slugカラムでリポジトリ名まで特定できる❗️

⚫◗ SELECT SKU, "Repository Slug", SUM(Quantity) ASSUMFROM'4b3a5d14_2025-02-06_180.csv'WHERE
        Product = 'Codespaces - Linux'AND
        SKU = 'Prebuild storage'ANDDateBETWEEN'2025-01-06'AND'2025-02-05'GROUPBY SKU, "Repository Slug";
┌──────────────────┬──────────────────────────────────────────────────┬───────────────────┐
│       SKU        │                 Repository Slug                  │        SUM│
│     varcharvarchar│      double       │
├──────────────────┼──────────────────────────────────────────────────┼───────────────────┤
│ Prebuild storage │ aws-application-workshop-using-localstack        │ 4.532299999999998│
│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 4.606599999999999│
└──────────────────┴──────────────────────────────────────────────────┴───────────────────┘

GitHub Codespaces prebuilds(プレビルド)のリポジトリ別の日別利用状況(2025年1月)

⚫◗ SELECT Date, SKU, "Repository Slug", Quantity ASSUMFROM'4b3a5d14_2025-02-06_180.csv'WHERE
        Product = 'Codespaces - Linux'AND
        SKU = 'Prebuild storage'ANDDateBETWEEN'2025-01-06'AND'2025-02-05'ORDERBY"Repository Slug", Date;
┌────────────┬──────────────────┬──────────────────────────────────────────────────┬────────┐
│    Date│       SKU        │                 Repository Slug                  │  SUM│
│    datevarcharvarchar│ double │
├────────────┼──────────────────┼──────────────────────────────────────────────────┼────────┤
│ 2025-01-06│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-07│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1403│
│ 2025-01-08│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-09│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-10│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-11│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-12│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-13│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-14│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-15│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-16│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-17│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-18│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-19│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-20│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-21│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-22│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-23│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-24│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-25│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-26│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-27│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-28│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-29│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-30│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-31│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-02-01│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-02-02│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-02-03│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-02-04│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-02-05│ Prebuild storage │ aws-application-workshop-using-localstack        │ 0.1464│
│ 2025-01-06│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-07│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1426│
│ 2025-01-08│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-09│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-10│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-11│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-12│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-13│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-14│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-15│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-16│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-17│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-18│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-19│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-20│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-21│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-22│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-23│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-24│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-25│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-26│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-27│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-28│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-29│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-30│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-01-31│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-02-01│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-02-02│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-02-03│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-02-04│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
│ 2025-02-05│ Prebuild storage │ aws-serverless-pattern-workshop-using-localstack │ 0.1488│
├────────────┴──────────────────┴──────────────────────────────────────────────────┴────────┤
│ 62rows4 columns │
└───────────────────────────────────────────────────────────────────────────────────────────┘

まとめ

Zenn Book で公開しているワークショップで GitHub Codespaces を使っていて,ストレージ使用量 90% のアラートが出たため,GitHub Usage Report と DuckDB を組み合わせて分析してみた🦆納得感のある内訳になっていることを確認できて良かった❗️

特に GitHub Codespaces prebuilds(プレビルド)は常に使用量が増えていくため,要注意だと思う.もしストレージ使用量が 100% になってしまうとプレビルドが使えなくなってしまうので,ワークショップ提供のことを考えると結構クリティカルな問題になってしまう.今後(2025年2−3月頃)に3つ目のワークショップを公開予定だったりもして,今後は GitHub Codespaces の追加課金も検討しようと思う💰️

docs.github.com

あとプレビルドではなく,通常のストレージ使用量で言うと,GitHub Codespaces の Default retention period30 days1 daysに変更しておくと良さそう(現在の個人の用途だと翌日に消えてしまって問題ないため).

docs.github.com

Viewing all 903 articles
Browse latest View live