Lambdaの関数をランタイムごとに数える
完成イメージ
$ abc lambda stats | RUNTIME | COUNT | |--------------------------|-------| | nodejs12.x | 2 | | nodejs8.10(Deprecated) | 1 | | python3.6 | 2 | | ruby2.5 | 8 | | ruby2.7 | 2 |
--verbose
オプションで関数名も表示
$ abc lambda stats --verbose | RUNTIME | COUNT | FUNCTIONS | |--------------------------|-------|-------------------------------| | nodejs12.x | 2 | sample-nodejs-12-function-1 | | | | sample-nodejs-12-function-2 | | nodejs8.10(Deprecated) | 1 | sample-nodejs-8-function | | python3.6 | 2 | sample-python-36-function-1, | | | | sample-pytohn-36-function-2 | | ruby2.5 | 8 | sample-ruby-25-function-1, | | | | sample-ruby-25-function-2, | | | | sample-ruby-25-function-3, | | | | sample-ruby-25-function-4, | | | | sample-ruby-25-function-5, | | | | sample-ruby-25-function-6, | | | | sample-ruby-25-function-7, | | | | sample-ruby-25-function-8 | | ruby2.7 | 2 | sample-ruby-27-function-1, | | | | sample-ruby-27-function-2 |
--format
オプションでJSONフォーマットで出力
$ abc lambda stats --format json | jq "." [ { "runtime": "nodejs12.x", "count": 2, "deprecated": false }, { "runtime": "nodejs8.10", "count": 1, "deprecated": true }, { "runtime": "python3.6", "count": 2, "deprecated": false }, { "runtime": "ruby2.5", "count": 8, "deprecated": false }, { "runtime": "ruby2.7", "count": 2, "deprecated": false } ]
CLIツール
もうブログで何回も紹介しているgo製のCLIツールabc
のサブコマンドとして実装する。
ランタイム一覧
現在サポートされているランタイムは以下。
- nodejs12.x
- nodejs10.x
- python3.8
- python3.7
- python3.6
- python2.7
- ruby2.7
- ruby2.5
- java11
- java8
- go1.x
- dotnetcore3.1
- dotnetcore2.1
- provided(カスタムランタイム)
ランタイムサポートポリシー
Lambdaのランタイムにはランタイムサポートポリシーというものが設定されていて、要はセキュリティパッチなどちゃんと実行環境がメンテナンスされているかを表すもの。
最新でないバージョンの言語のランタイムは随時廃止されて行く模様。
廃止は段階的に行われる。
まずはそのランタイムで新しい関数が作成できなくなり、続いて既存の関数の更新ができなくなる。 その後は、既存の関数は廃止されることなくそのまま使い続けることはできるが、実行環境のメンテナンスは行われないので移行が推奨される。
すでに廃止されているランタイムは以下。
- dotnetcore1.0
- dotnetcore2.0
- nodejs(Node.js 0.10)
- nodejs4.3
- nodejs4.3-edge
- nodejs6.10
- nodejs8.10
現在(2020/06/08)時点で廃止予定のランタイムはないが、Python2.7まわりはざわざわしている。 2020/12/31までセキュリティパッチは当てられるようだが、いつ廃止されるという明記はない。
実装
lambda:ListFunctions
APIで関数が列挙できる。
再帰的に取得して結果を返す部分の抜粋。
func listFunctions() ([]*lambda.FunctionConfiguration, error) { var result []*lambda.FunctionConfiguration var nextMarker *string for { params := &lambda.ListFunctionsInput{ MaxItems: aws.Int64(1000), Marker: nextMarker, } resp, err := LambdaClient.ListFunctions(params) if err != nil { return nil, err } result = append(result, resp.Functions...) if resp.NextMarker == nil { break } nextMarker = resp.NextMarker } return result, nil }
パラメータは、なるべく1回で取得するためにMaxItems
を大きめにとっている。次ページがある場合はMarker
を指定して再帰的に取得する。他のAPIではNextToken
という実装をよく見るがMarker
は初めて見た🤔
他にもバージョンの指定やLambda@Edge用にリージョンの指定もできるのだが今回は必要ないので割愛。
集計はランタイムごとにmap
に突っ込んでいて、ソートされたキー順に出力するのだが、一点微妙なところがある。Node.jsのようにバージョンが2桁になっているランタイムでは、リリース順にきれいに並ばないみたいなことが起きる。
(例) 昇順にソートしているが、一番上に最新のnodejs12.xがきてしまう
RUNTIME | COUNT |
---|---|
nodejs12.x | 2 |
nodejs10.x | 2 |
nodejs8.10(Deprecated) | 1 |
python3.6 | 1 |
python3.7 | 1 |
python3.8 | 2 |
ruby2.5 | 8 |
ruby2.7 | 2 |
これを考慮したソートを実装するのはちょっと骨が折れるので今回はやらなかった。
出力のフォーマットはマークダウンのtableかJSONを選択できるようにした。
これまではJSON縛りで作ることが多かったが、今回のユースケースではマークダウンにペッと貼り付けたいこともあるかなと思ったので。
func Output(count map[string][]string) (string, error) { if format == "json" { return jsonOutput(count) } else if format == "table" { return tableOutput(count), nil } return "", errors.New("invalid format.") }
JSONは代わり映えしないので省略するとして、テーブル形式でのフォーマットの実装に関して。
最初は標準のtext/tabwriter
を試した。
最近リリースされたAWS謹製のec2-instance-selector
ではこれを使ってテーブル出力を実装している。
使い方もわかりやすく標準で賄えるのはいいのだが、凝ったことをしようとするとなかなか厳しい。それに、色をつけようとするとタブ幅が崩れるという問題があった。
代わりに以下のライブラリを導入した。
こちらではマークダウン形式のテーブルがサポートされていて、表崩れもなかったのでめでたく採用。
柔軟な色付けもできるっぽい。
(柔軟と言いつつ今回は実装が煩雑になるので自前でカラーコードを埋め込んだが)
func tableOutput(count map[string][]string) string { keys := sortKey(count) tableString := &strings.Builder{} table := tablewriter.NewWriter(tableString) table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) table.SetCenterSeparator("|") if verbose { verboseTableOutput(keys, table, count) } else { normalTableOutput(keys, table, count) } return tableString.String() } func normalTableOutput(keys []string, table *tablewriter.Table, count map[string][]string) { table.SetHeader([]string{"Runtime", "Count"}) for _, k := range keys { runtime := k if isDeprecatedRuntime(runtime) { runtime = fmt.Sprintf("\x1b[91m%s(Deprecated)\x1b[0m", runtime) } table.Append([]string{runtime, strconv.Itoa(len(count[k]))}) } table.Render() }
廃止済みのランタイムを使っている場合は赤くして警告する。
v0.5.0
としてリリース