AWSCodeBuildの速度改善をドヤ顔で魅せたい人のために可視化ツールを作った話
概要
AWS CodeBuildの実行時間を短縮にチャレンジしていて、その改善効果を、おしゃれに魅せたい!!そんなときはありませんか?(私はあります)
本稿では、よくAWS CodeBuildを利用していて、実行時間の短縮などにもチャレンジしている筆者が、改善の歴史を魅せるのに、ちょうどよいツールがなかったので、AwsCodeBuildVizという可視化ツールを作ったので、紹介します。
見た目と機能とアーキテクチャは、こんな感じです
見た目
機能
- AWS CodeBuildの結果を取得する機能
- 2回目以降の実行では差分取得するので効率的に取得できる
- AWSにアクセスするための認証は、2種類の方式から選択できる
- AWS SSO
- AWS_ACCESS_KEYとAWS_ACCESS_SECRET
- AWS CodeBuildの結果を可視化する機能
- 以下の切り替えをインタラクティブにできる
- 集計を日毎/月毎に切り替えできる
- 表示単位を日毎に指定できる
- Phase(Build,PreBuild,PostBuildなど)事に可視化ができる
- 以下の切り替えをインタラクティブにできる
- Docker上で動作
- ローカル環境を汚さずに実行ができる
- DenoとViteを利用しており、高速に動作します
アーキテクチャ
技術的な話
AwsCodeBuildVizは、以下の2つの要素で構成されています。
- AWS CodeBuildの実行履歴を取得するためのcliツール部分
- Denoベースで、キャッシュ用にDeno KVを利用
- 取得した履歴を可視化するための、可視化ツール部分
- DenoベースのViteアプリで、React-TypeScriptを利用
cliツール部分
cliツール部分は、以下のような構成にしています。
Deno1.28頃から、npmをサポートしたという話があり、実用的に利用できるか試してみたかったので、Denoを採用しています。
また、CodeBuildの結果をキャッシュしておくために、Deno KVというデータストアを利用しています。
- Deno1.41.2を利用
- AWSへのアクセス部分には、@aws-sdk(npm)を利用
- CodeBuildの結果のキャッシュ用に、Deno KVを利用
npm連携はどんな感じ?
今回は、npmで提供されているaws-sdkをDenoから利用してみましたが、今回の範疇では、特段問題なく動かすことができました。
例えば、以下は、CodeBuildの結果のid一覧を取得している部分のコードですが、importする際に、'npm:@aws-sdk/client-codebuild'のようにnpmベースのライブラリであることを示すだけで、サクッと動きました。
(私のローカル開発環境では、型の情報が、イマイチ反映されなかったので、本格的に開発に利用する場合は、LSP周りの設定などを、工夫する必要があるかもしれません。型推論が弱いだけで、明示的に型の情報を指定すれば問題ありません。)
import { CodeBuildClient, ListBuildsForProjectCommand, ListBuildsForProjectCommandInput, ListBuildsForProjectCommandOutput } from "npm:@aws-sdk/client-codebuild";
const createClient = (credentials:unknown,region:string) => {
const client = new CodeBuildClient({
region: region,
credentials: credentials,
});
return client;
};
const listBuildsOnce = async (client:unknown, buildIdsResult:string[], codeBuildProjectName: string, nextToken?: string) => {
const input:ListBuildsForProjectCommandInput = {
projectName: codeBuildProjectName,
nextToken: nextToken,
};
const command = new ListBuildsForProjectCommand(input);
const response:ListBuildsForProjectCommandOutput = await client.send(command);
nextToken = response.nextToken;
for(const buildId of response.ids) {
buildIdsResult.push(buildId);
}
if(nextToken) await listBuildsOnce(client, buildIdsResult, codeBuildProjectName, nextToken);
}
export const ListBuilds = async (credentials:unknown, codeBuildProjectName: string, region: string):Promise<string[]> => {
const client = await createClient(credentials, region);
const buildIdsResult:string[] = [];
await listBuildsOnce(client, buildIdsResult, codeBuildProjectName);
return buildIdsResult;
};
Deno KVはどんな感じ?
CodeBuildの情報を毎回取得せず、差分実行を実現するために、何かしらのやり方で、取得結果をキャッシュさせたかったのですが、今回は、Deno KVという機能を使ってキャッシュしてみました。
ローカル環境で実行する場合は、実態はsqliteを利用しているようでした。
例えば、
「CodeBuildの詳細情報がローカルになかった時だけ取得する」という機能を実現したい場合は、以下のように、シンプルに実現することができ、非常に強力な機能でした。
const kv = await Deno.openKv('./denoKVData/kv.sqlite3');
const buildIds = await ListBuilds(credentials, codeBuildProjectName, region);
// CodeBuildの実行都度払い出されるbuildIDは、全件取得してDeno KVに保存
// CodeBuildの実行履歴は、自動的に消えていくので保存しておく
for(const buildId of buildIds) {
await kv.set( [codeBuildProjectName, buildId], { codeBuildProjectName, buildId });
}
const entries = kv.list({ prefix: [codeBuildProjectName]});
for await (const entry of entries) {
const buildId:string = (entry.value as { buildId: string }).buildId;
const buildDetailEntry:BatchGetBuildsCommandOutput = await kv.get([`${codeBuildProjectName}__detail-response`, buildId]);
// キャッシュがない場合だけ、詳細情報を取得して、キャッシュしておく
// キャッシュがある場合は、詳細情報を取得しない
if( buildDetailEntry.value === null || buildDetailEntry.value.response === undefined) {
const response = await BatchGetBuilds(credentials, buildId, region);
await kv.set(
[`${codeBuildProjectName}__detail-response`, buildId],
{ response },
);
}
}
AWS SSOとの連携はどんな感じ?
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYを利用する場合は、特に工夫することはなかったのですが、aws cliのaws sso loginによるログイン機能と連携をしたかったので、以下のような工夫をしました。
まず、AWS_ACCESS_KEY_IDを利用する場合は、必要なトークンを設定した上で、以下のようにdocker compose(compose.yaml)を使って、実行できるようにしてあります。
docker compose run --rm cli
AWS SSOを利用する場合は、dockerのホストマシン側で、aws sso loginを実行した時に生成される、credentialの情報をvolumeマウントすることで、共有するようにしました。
compose.yamlとは別に、compose.sso.yamlを作成することで実現しています。
services:
cli:
volumes:
# 中略
- ~/.aws:/root/.aws:ro
environment:
- AWS_CONFIG_FILE=/root/.aws/config
# 後略
docker compose -f compose.yaml -f compose.sso.yaml run --rm cli
可視化ツール部分
可視化ツール部分は、Viteを使って、React-TypeScriptで構築しています。
ViteアプリをDenoで動かす
ViteアプリをDeno上で動かせないか調べてみたところ、Denoの公式ドキュメントに記述があり、そのとおりの手順を踏むことで、Deno上でViteを動かすことができました。
生成されたアプリに同梱されていた、Vite with Denoのアイコンがとってもかわいいです。
生成されたアプリを眺めてみると、さきほどのcliのところのように、'npm:xxxxxx'のように指定しているわけではなく、NodeJS由来のpackage.jsonを利用しており、node_modulesをそのまま利用しているようでした。
生成されたdeno.jsonでは、--node-modules-dirというオプションがついており、package.json経由でinstallされたnode_modulesを利用しています。
{
"tasks": {
"dev": "deno run -A --node-modules-dir npm:vite"
}
}
Denoっぽい記述は、以下のようなvite.config.mtsだけでした、
ほかは、NodeJSで作った場合とまったく同じものが動きました。
import { defineConfig } from 'npm:vite@5.1.6';
import react from 'npm:@vitejs/plugin-react@^4.2.1';
import 'npm:react@^18.2.0';
import 'npm:react-dom@^18.2.0';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: true
},
});
実際のアプリ側は、以下のような感じで、Deno特有の記述が一切ありません。
import React from 'react'
import ReactDOM from 'react-dom/client'
import { App } from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
既存のNodeJSベースのアプリを、Denoに移植するための敷居が、とても低くなっているので感動しました。
フロントエンドの主要ライブラリ
Deno上でViteアプリを動かす上で、node_modulesをそのまま利用できたため、特筆する事項がないので、利用しているnpmライブラリを紹介して終わります。
フロントエンドの主要ライブラリ(カッコ部分はメジャーバージョン)は以下のとおりです。
- TypeScript(5)
- React(18)
- chart.js(4)
- react-chartjs-2(5)
グラフ描画にchart.js、インタラクティブに表示を切り替えたいので、Reactを利用しています。
特にこれといった工夫をすることはなく、集計単位の切り替えや、表示対象期間の切り替えがぬるぬるうごいていい感じです。
おわりに
AWS CodeBuildの実行時間を、日毎や月毎に集計して、インタラクティブに表示するAwsCodeBuildVizという可視化ツールを作ったので、中身の技術を紹介しました。
cliツールだけではなく、ブラウザアプリ側もすべてDenoで構築するチャレンジをしたのですが、Denoがnpmだけではなく、node_modules/をディレクトリごとそのまま利用できるようになっていたため、NodeJS -> Denoへの移行の敷居が極めて低くなっていることを実感しました。
Docker上で簡単に動かせるようにしていますので、AWS CodeBuildの実感の推移を、魅せたくなったときは、是非ご活用いただければ幸いです。
Discussion