Toyokumo Tech Blog

トヨクモ株式会社の開発本部のブログです。

AWS CLI を Babashka スクリプトでラップして楽しよう

開発本部の飯塚です。

業務で Babashka を使う機会を無理矢理作ってみたので、その副産物を公開してみます。

動機

  • CloudWatch Logs からログを抽出して調査したい
  • AWS CLI を使ってもいいが、クエリの発行、クエリの完了待ち、結果取得が面倒
  • 良い感じにラップしたスクリプトを用意して誰でも簡単に使えるようにしたい
  • Clojure で!!

Babashka

Babashka@borkdude 氏が開発している Clojure のコードを スクリプト として高速に実行できる環境です。 高速さの背景には GraalVM を利用したネイティブイメージ化があります。 これにより JVM 言語で度々話題にあげられるスタートアップタイムがほぼ無視できるくらいの速度で起動してくれるのが大きな特徴です。

$ time clojure -e '(+ 1 2 3)'
6
clojure -e '(+ 1 2 3)'  1.47s user 0.10s system 195% cpu 0.806 total

$ time bb '(+ 1 2 3)'
6
bb '(+ 1 2 3)'  0.00s user 0.01s system 23% cpu 0.070 total

今回はこの Babashka を使って AWS CLI をラップしたスクリプトを作ってみました。

前提

この記事で紹介しているコードは使い方は以下が済んでいることを前提としています。

  • AWS CLI がインストールされていること
  • aws configure で鍵情報などが設定済みであること

作ったもの

コードが少し長かったので Gist に置いてあります。

AWS CLI wrapping script by Babashka

先頭で require している cheshire.core は Babashka にバンドルされているもので、何もしなくてもデフォルトで使うことができます。 他に何がデフォルトで使えるのかなどは README の Usage に書かれています。

基本的には aws.logs.core/query を使えば AWS CLI を通じてログを取得できるようにしています。 取得するログの範囲は指定した日付の 00:00:00 から翌日の 00:00:00 としています。

もし aws.logs.core/query では自動化されすぎてやりたいことができないという場合は、 aws.logs.core/query から呼んでいる関数群を時前で制御すれば大抵のことはできるように関数を分割しているつもりです。

使い方

まずこれから紹介するコード類は以下のようなディレクトリ構造になっていることを前提とします。

- run.sh
- src
   |
   +- aws
   |   |
   |   +- logs
   |       |
   |       +- core.clj
   +- example
       |
       +- core.clj

最初に aws.logs.core を呼び出すコードです。 以下ではログのメッセージに "NullPointerException" が含まれるものだけを抽出してみています。 フィルタリングなどのクエリについては CloudWatch Logs Insights クエリ構文 を参照してください。

(ns example.core
  (:require
   [aws.logs.core :as logs]))

(defn -main [& args]
  (when (< (count args) 3)
    (System/exit 1))

  (try
    (let [[yyyy mm dd & [region]] args
          filter-query  "filter (@message like \"NullPointerException\")"]
      (doseq [result (logs/query {:group-name "/aws/path/to/your.log"
                                  ;; aws configure しているリージョンとは違うリージョンにしたい場合は指定可能
                                  :region region
                                  :yyyy yyyy :mm mm :dd dd
                                  :queries [filter-query]})]
        (println result))
      (System/exit 0))
    (catch clojure.lang.ExceptionInfo ex
      (println (.getMessage ex) (ex-data ex))
      (System/exit 1))))

次に実行するためのスクリプトです。 無くても実行できますが用意しておくとクラスパスを都度指定する必要がなくなり便利です。

#!/bin/bash
SCRIPT_DIR=$(cd $(dirname $0); pwd)
bb --classpath ${SCRIPT_DIR}/src -m example.core -- $@

あとは以下のように年月日を指定して実行すればログを取得して出力してくれます。 今回の例では取得したログをそのまま出力してしまっていますが、必要に応じて欲しい情報だけピックアップして出力しても良いでしょう(業務で使っているスクリプトではそうしてます)

./run.sh 2112 9 3

最後に

ここで紹介したスクリプトは Clojure を使うことを除けば他の言語では大して目新しくないものでしょう。 このスクリプトの良いところは Clojure で書けるというだけでなく、REPL 駆動で開発できるということが個人的にはかなり大きいです。 取得したログから必要な情報だけピックアップするにしても、REPL 駆動であれば一時的に結果を適当な var に束縛しておいてあれこれ試すことは容易です。

そうして作ったスクリプトがスタートアップタイムを気にせずに実行できるので開発体験としてはとても良いものがあると思います。

もし REPL 駆動での開発に興味があれば Vim で始める Clojure 開発 も併せて参照してください。


トヨクモでは一緒に働いてくれるエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。