サポートが終了した Runtime を使っている AWS Lambda の関数を見つけるスクリプト

前回の記事にも書きましたが、先月末(2021年4月末)ころに AWS から AWS Lambda の Node.js 10.x Runtime のサポートが終了しますというお知らせが来ていました。

それで仕事や個人で使っている AWS アカウントの Lambda 関数の Runtime をちょっと見てみたところ、Node.js 10.x どころか nodejs8.10 だの nodejs6.10 だの、しまいには無印の nodejs(Node.js のバージョンは 0.10 らしいです)なんかも見つかり、これはいかんということで全アカウントの全リージョンの全 Lambda 関数を棚卸ししてみることにしました。

しかし仕事のアカウントでは Lambda 関数の数は(Runtime のバージョンが問題ないものも合わせて)1,000 をはるかに超えていて、各関数にも複数のバージョンがあります。アカウントもいろいろな用途等に応じて複数の本番環境があったり開発環境があったりなどして複数にまたがっていますし、それら複数のアカウントでそれぞれ複数のリージョンを利用していたりします。

そんなわけで、手作業で調べるのでは時間がいくらあっても足りなさそうです。

ということで AWS CLI の出番です。

AWS からのお知らせに書かれていた、aws lambda list-functions コマンドの例をベースにして改良を加えていった結果、以下のようなスクリプトが出来上がりました。

#!/usr/bin/env bash
set -Eeuo pipefail

profiles=(production staging development) # replace this with your aws account profiles

outdated_runtimes="$( curl -s https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html 2>&1 | sed -n 's|.*<code class="code">\([^<]*\)</code>.*|\1|gp')"
select_outdated_runtime="$( echo "$outdated_runtimes" | sed 's/^\(.*\)$/"\1"/' | sed 's/^/.Runtime == /g' | sed ':a; N; $!ba; s/\n/ or /g' )"

for profile in "${profiles[@]}"; do
  regions="$( aws ec2 describe-regions --query "Regions[*].[RegionName]" --output text --profile "$profile" --region ap-northeast-1 )"
  for region in $regions; do
    >&2 echo -e "\\033[1;31m$profile - $region\\033[0m"
    functions="$( aws lambda list-functions --function-version ALL --profile "$profile" --region "$region" | jq -r '.Functions[] | select('"$select_outdated_runtime"') | [.Runtime, .FunctionName] | @csv' | sort | uniq )"
    for fn in $functions; do
      echo "$profile,$region,$fn"
    done
  done
done

実行には aws jq curl などのコマンドが必要です。

手元の Ubuntu 20.04 では動作していますが、mac だと sed のあたりの挙動の違いが原因で期待通りに動かない可能性があります。

このスクリプトをお使いになる際には 4 行目の profiles のカッコの中をご自身のご利用になっている AWS アカウントのプロファイル名(aws configure --profile <profile> で指定したプロファイル名)に置き換えてください。

「プロファイルってなんのこと?」という方はおそらく profiles=(default) とかすると良いかと思います。

このスクリプトを実行すると、以下のような感じでプロファイル名、リージョン名、ランタイム名、関数名を CSV 形式で出力してくれます。(進捗状況を示すために stderr に表示している プロファイル名 - リージョン名 は省略)

production,ap-northeast-1,nodejs10.x,hoge-fuga-func
production,ap-northeast-1,nodejs8.10,foo-bar-func
production,us-west-2,python2.7,test
development,ap-northeast-1,nodejs,hello-world

.csv な拡張子を持つファイルに結果をリダイレクトして保存すると、それを Excel などで開いて閲覧・編集ができます。

解説

半年後の自分のために備忘録的に一応解説を書き残しておきます。

1〜2 行目はボイラープレートというか、bash スクリプトを書くときのお決まりの書き出し部分ですね。 詳しいことは以下の記事を参考にするとよいのではないでしょうか。

betterdev.blog

4 行目の profiles= の行は先ほど書いたとおり AWS アカウントのプロファイル名を配列として列挙します。ここに書いた各プロファイルに対して Lambda 関数をチェックしていきます。

6 行目では、AWS の以下のサイトをスクレイピングして、サポートが終了した(もしくは近いうちに終了する)ランタイムの一覧を取得しています。(もっと良い方法があったらお知らせください)

docs.aws.amazon.com

なおこの方法は同僚の Jed が教えてくれました。jed++

もし上記サイトの URL や内容が変化してうまくスクレイピングできなくなったら

outdated_runtimes="python2.7
ruby2.5
      :
   (省略)
      :
dotnetcore1.0"

のように手で直接書くようにしてみてください。

6 行目で取得したランタイムの一覧は以下のような感じになります。

python2.7
ruby2.5
nodejs10.x
nodejs8.10
nodejs6.10
nodejs4.3-edge
nodejs4.3
nodejs
dotnetcore2.0
dotnetcore1.0

これを 7 行目で加工して、以下のような文字列に変換しています。

.Runtime == "python2.7" or .Runtime == "ruby2.5" or .Runtime == "nodejs10.x" or .Runtime == "nodejs8.10" or .Runtime == "nodejs6.10" or .Runtime == "nodejs4.3-edge" or .Runtime == "nodejs4.3" or .Runtime == "nodejs" or .Runtime == "dotnetcore2.0" or .Runtime == "dotnetcore1.0"

これはあとで jqselect で対象の Runtime を見つけるのに使っています。

9 行目から各プロファイルに対してループを開始します。

10 行目では、そのプロファイルで利用可能なリージョンの一覧を取得します。

11 行目から各リージョンに対するループを開始します。

12 行目は進捗状況を stderr(標準エラー出力)に出力しています。

13 行目は aws コマンドで関数の一覧を取得し、jq でサポート切れの Runtime のみを抽出して、その Runtime と関数名を CSV 形式で出力しています。aws lambda list-functions--function-version ALL を指定しているので、同じ関数の過去のバージョンもすべてチェックしますが、そのせいでバージョンが違うだけの同じ名前の関数が複数出力されます。それを | sort | uniq で重複排除しています。

14〜16 行目では、関数の一覧にプロファイル名とリージョン名も付けて CSV 形式で出力しています。

余談

私は以前 Go で Lambda のコードを書くために apex を利用していました。現在は Go の Runtime が AWS から公式に提供されていますが、数年前は Go の Runtime が無かったので Node.js を Runtime として用いて Node.js のコードから Go のプログラムを起動するというような方法で実行していました。apex はそのあたりをうまいこと隠蔽してくれていたので使っていたという背景があります。

Go のRuntime が正式にサポートされるようになってからは apex も Go の Runtime を直接使ってくれるようになりましたが、過去に Node.js ランタイムを使ってアップロードされたバージョンはそのまま残っていました。

今回検出された古い Node.js ランタイムの大部分はそのような経緯で作られたもので、すでに使われていない(Go Runtime に移行済みの)関数のバージョンばかりでした。

なお今は apex の開発が停止してしまったのでいろいろな代替ツールを試しましたが、私は個人的には AWS CDK に落ち着いています。