
SSM InventoryでEC2にインストールされたソフトウェアのEOL管理を自動化してみた
こんにちは。運用イノベーション部の小形です。
以前Azure基盤の運用保守を担当していた時に、VMにインストールされたソフトウェアのEoL(サポート終了)を、Excelの台帳で下記のような手順で手動管理していたことがありました。
- サーバーにログインしてバージョンを調べる
- 紙のメモにバージョンを書く
- Excelの台帳に転記する
- EoL日と照らし合わせる
この手順には次のような課題を感じていました。
- 棚卸しのたびに申請や承認など手続きが発生する
- 手入力なので更新漏れや入力ミスで台帳と実態がずれる
- 通知の仕組みもないので台帳を見る人が気づかないとEoLを見落とす
- 台帳を更新する特定の人に、作業が集中する(実際属人化していた)
そこで今回はこの棚卸しと期限の確認を仕組みにしてみました。AWS Systems Managerの機能の1つであるSSM InventoryでEC2上のNode.jsを収集し、endoflife.date のAPIでEoL日を判定して、EoLを迎えた(または迎えそうな)ものをGitHub Issueとして起票します。バージョンを上げると、次回の実行でIssueが自動でクローズされます。
なお、自動化したのは「検知」と「Issueでの追跡」までで、バージョンアップ自体は人が行います。
今回作った仕組みの全体像
今回作った仕組みの構成はこちらです。

EC2上のNode.jsのバージョンをSSM Inventoryで収集し、毎日EventBridge SchedulerでLambdaを起動して確認します。EoLを迎えた、または迎えそうなものがあれば、GitHub Issueとして起票します。
処理の流れ
処理の流れは下記です。番号は構成図に記載の番号に対応しています。
- Systems ManagerのState ManagerでEC2から導入済みソフトウェアの情報を収集する。
- 収集した情報をSSM Inventoryに保存する。
- EventBridge Schedulerが指定した日時にLambdaを起動する。
- LambdaがSSM InventoryからNode.jsのバージョンを確認する。
- Lambdaが endoflife.date のAPIからEoL日を取得して期限を判定する。
- EoLが近い、または期限切れのものをGitHub Issueとして起票する。重複して起票しないよう、起票状態はDynamoDBに記録する。
- 運用担当者がIssueを確認する。
- 運用担当者がバージョンアップする。次回のLambda実行でEoLの対象から外れると、Issueが自動でクローズされる。
判定のルール
Issueを起票する条件は、次のとおりです。
- すでにEoLを迎えている
- EoLまで90日以内である
条件は以前の運用でEoLの90日前を目安に確認していたことを参考にしました。
環境や更新にかかる期間に合わせて変えられるよう、Lambdaの環境変数で設定できるようにしています。
検証環境
構成のポイントだけ説明します。
EC2
nvmでインストールしたNode.jsがInventoryに拾われなかった経験があったため、
Node.jsの入れ方が異なるEC2を2台用意して確認しました。
| 名前 | OS | Node.jsの入れ方 | バージョン |
|---|---|---|---|
| web-old | Amazon Linux 2023 | dnf | v20.20.2 |
| web-nvm | Amazon Linux 2023 | nvm | v20.20.2 |
どちらも、次のようにインストールしました。
# web-old (dnf)
sudo dnf install -y nodejs20
# web-nvm (nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.nvm/nvm.sh
nvm install 20
Node.js 20は、endoflife.date によると2026年4月30日にセキュリティサポート終了を迎えています。
そのため、今回の「EoL済み」の検証対象として使いました。
ネットワークと権限
- パブリックサブネットに配置しパブリックIPを割り当てています。NATゲートウェイやVPCエンドポイントは作らず、SSM Agentがパブリックなエンドポイントに直接接続する、安価な構成です
- セキュリティグループのインバウンドルールは、すべて削除しています。EC2への接続は、SSMのセッションマネージャーで行いました
- EC2にはSSMの管理対象とするために
AmazonSSMManagedInstanceCoreポリシーを付けたIAMロールを割り当てています
SSMのフリートマネージャーで、2台とも「オンライン」になることを確認しました。

作成したリソース
今回の検証で作成したリソースの一覧です。
| 種類 | 名前 | 用途 |
|---|---|---|
| EC2 | web-old / web-nvm | 検証対象 |
| IAMロール | eol-ec2-role / eol-lambda-role | EC2用 / Lambda用 |
| State Manager | eol-gather-inventory | Inventoryの収集 |
| State Manager | eol-nvm-custom-inventory | 「nvm版をカスタムインベントリで補う」の章 |
| Systems Manager Parameter Store | /eol-tracker/github-token | GitHubのトークン(SecureString) |
| DynamoDB | eol-tracker | Issueの起票状態 |
| Lambda | eol-tracker | EoLの判定とIssueの起票(Python 3.14) |
| EventBridge Scheduler | eol-tracker-daily | Lambdaの定期実行 |
| GitHubリポジトリ | eol-tracker(Private) | Issueの起票先 |
リージョンは、すべて東京(ap-northeast-1)です。
nvmでインストールしたNode.jsはSSM Inventoryで拾われなかった
今回用意した2台でSSM InventoryがEC2上のNode.jsを収集できるかを検証しました。
結論、dnfでは拾えましたがnvmでは一覧に表示されませんでした。
Inventoryの収集設定
State Managerの関連付けで、Inventoryの収集を設定しました。
| 項目 | 値 |
|---|---|
| ドキュメント | AWS-GatherSoftwareInventory |
| Applications | Enabled |
| Custom Inventory | Enabled |
| ターゲット | web-old、web-nvm |
| スケジュール | 30分ごと |
収集が終わると、フリートマネージャーの各インスタンスの「インベントリ」タブで、AWS:Application を確認できます。名前が nodejs20 のものを探します。
dnfで入れたNode.js(web-old)
web-old では、nodejs20 が表示されました。

nvmで入れたNode.js(web-nvm)
一方、web-nvm では、同じ条件で絞り込んでも何も表示されませんでした。

web-nvm のInventoryには、他のアプリケーションが500件近く収集されていました。
収集自体は動いていて、Node.jsだけが含まれていません。
なぜ出ないのか
GitHubで公開されているSSM Agentのソースコードを確認してみました。Linux向けの処理(dataProvider_unix.go)を見ると、dpkg-query や rpm -qa などのパッケージ管理のコマンドで、アプリケーションの一覧を取得していることが分かります。
実際に web-nvm で rpm を確認したところ、Node.jsは表示されませんでした。
rpmで一覧に出てこないため、Inventoryにも収集されなかったと考えられます。
web-oldのrpm確認結果↓

web-nvmのrpm確認結果↓

次の章でこの検知漏れを補います。
nvm版をカスタムインベントリで補う
標準のInventoryに出ないnvm版のNode.jsは、カスタムインベントリで補います。
カスタムインベントリとは
SSM Agentは、決められたディレクトリにJSONファイルを置くと、その内容をカスタムインベントリとして収集します。
- 配置先(Linux):
/var/lib/amazon/ssm/<インスタンスID>/inventory/custom - 必須のキー:
SchemaVersion、TypeName、Content TypeNameはCustom:から始める
詳細は、AWSのドキュメントを参照してください。
つまり、nvmで入れたNode.jsを自分で調べて、このJSONを書き出せば、Inventoryに載せられます。
nvm版のNode.jsを調べるスクリプト
nvmは、~/.nvm/versions/node/ 配下に、バージョンごとのディレクトリを作ります。これを探して、JSONを書き出します。
# ~/.nvm/versions/node/ 配下のバージョンを探す
for d in /home/*/.nvm/versions/node/v* /root/.nvm/versions/node/v*; do
[ -d "$d" ] || continue
VER=$(basename "$d" | sed 's/^v//') # v20.20.2 → 20.20.2
ITEM="{\"Name\":\"nodejs\",\"Version\":\"${VER}\",\"InstallPath\":\"${d}\"}"
ITEMS="${ITEMS:+${ITEMS},}${ITEM}"
done
# SSMが収集するJSONとして書き出す
echo "{\"SchemaVersion\":\"1.0\",\"TypeName\":\"Custom:NodeJS\",\"Content\":[${ITEMS}]}" > "${DIR}/NodeJS.json"
${DIR} は、上で説明した配置先のディレクトリです(インスタンスIDは、インスタンスメタデータから取得しています)。
このスクリプトはState Managerの関連付け(AWS-RunShellScript)で、web-nvm に対して30分ごとに実行します。nvmでバージョンを入れ替えた場合も、次の実行でJSONが更新されます。
結果
スクリプトの実行結果として、次のJSONが出力されました。
{"SchemaVersion":"1.0","TypeName":"Custom:NodeJS","Content":[{"Name":"nodejs","Version":"20.20.2","InstallPath":"/home/ssm-user/.nvm/versions/node/v20.20.2"}]}

このJSONは、Custom Inventory を有効にしたInventoryの収集で、SSM Inventoryに取り込まれます。次の収集を待たずに確認したい場合は、収集の関連付け(eol-gather-inventory)を「今すぐ適用」で実行します。
フリートマネージャーで、インベントリタイプを Custom:NodeJS に切り替えると、nvm版のNode.jsが表示されました。

EoLの判定とIssueの起票
Inventoryで集めたNode.jsのバージョンから、EoLを判定してIssueを起票する処理をLambda(Python)で実装しました。
endoflife.dateとは
endoflife.date は、さまざまなソフトウェアのEoL日をまとめた無料のサービスで、APIでも取得できます。
- 対象製品の一覧は、トップページで確認できます。Node.jsやPythonなどのほか、Amazon CorrettoやAmazon EKSなどのAWS関連の製品も含まれています
- APIの仕様は、ドキュメントにまとまっています
今回は、次のAPIでNode.jsのメジャーバージョンごとのEoL日を取得しました。
res = http("GET", "https://endoflife.date/api/v1/products/nodejs")
return {r["name"]: r["eolFrom"] for r in res["result"]["releases"] if r["eolFrom"]}
# {"20": "2026-04-30", "22": "2027-04-30", ...}
Lambdaの処理
Lambdaの処理は、大きく3つです。
1. Node.jsを検出する
SSMが管理している全インスタンスから、Node.jsを探します。dnf版は AWS:Application から、nvm版は「nvm版をカスタムインベントリで補う」の章の Custom:NodeJS から取得します。
# nodejs / nodejs20 / nodejs22 だけを対象にする(nodejs20-npm などは除外)
PKG_RE = re.compile(r"^nodejs\d*$")
検出した結果は、<インスタンスID>#nodejs#<メジャー番号> をキーにして、バージョンと入れ方(dnf / nvm)を保持します。
2. EoLを判定する
メジャーバージョンのEoL日と、今日の日付を比べます。
days = (date.fromisoformat(eol) - today).days
if days <= THRESHOLD: # 90日。EoL済みなら days はマイナス
at_risk[key] = {...}
3. Issueを起票する
対象のものを、GitHub Issueとして起票します。
起票して終わりではなく、対応が済むと閉じる、という形で、対応状況も追跡できます。今回は使い慣れているGitHubにしましたが、Amazon SNSのメール通知やSlackへの通知は、今後、実運用に近づけるときに試したいと考えています。
同じ内容を毎日起票しないよう、DynamoDBに状態を記録して重複を防ぎます。
item = table.get_item(Key={"pk": key}).get("Item")
if item and item["status"] == "OPEN":
continue # すでに起票済みなら何もしない
起票したら、DynamoDBに status: OPEN として、Issue番号とEoL日を保存します。
GitHubのトークンは、Parameter Store(SecureString)に保存しておき、Lambdaの実行時に取得します。トークンには、対象リポジトリのIssueの読み書き権限だけを付けました。
手動で実行してみる
Lambdaを手動で実行しました。
{
"at_risk": ["i-xxxx#nodejs#20", "i-yyyy#nodejs#20"],
"opened": ["i-xxxx#nodejs#20", "i-yyyy#nodejs#20"],
"closed": []
}

web-old(dnf)と web-nvm(nvm)の2台とも、Node.js 20がEoL済みとして検出され、2件のIssueが起票されました。

Issueには、インスタンスID、検出バージョン(dnf / nvm)、EoL日、検出日が記載されます。web-nvm のIssueにも nvm と記載されており、カスタムインベントリで補った情報が使われていることが分かります。


もう一度、Lambdaを実行しても、Issueは増えませんでした。opened が空になり、重複して起票されないことを確認できました。
定期実行する
ここまでは、Lambdaを手動で実行してきました。EventBridge Schedulerで、定期実行できるようにします。
スケジュールの設定
EventBridge Schedulerで、Lambdaを呼び出すスケジュールを作成しました。
| 項目 | 値 |
|---|---|
| スケジュールのパターン | 定期的なスケジュール(cronベース) |
| タイムゾーン | Asia/Tokyo |
| ターゲット | AWS Lambda の Invoke(eol-tracker) |
| ペイロード | {} |
| 実行ロール | このスケジュールの新しいロールを作成 |

実際の運用では、「毎日決まった時刻」で実行します。今回は動作確認のため、cronを作成の約5分後に実行される時刻にしました。
Schedulerから起動できたか確認する
設定した時刻のあと、Lambdaのロググループ(/aws/lambda/eol-tracker)に、新しいログストリームが作成されました。

ログの出力は、次のとおりです。
{"at_risk": ["i-xxxx#nodejs#20", "i-yyyy#nodejs#20"], "opened": [], "closed": []}
opened が空なので、すでに起票済みのIssueは、重複して起票されませんでした。手動で2回実行したときと同じ動作です。
これで、Schedulerからの定期実行を確認できました。
バージョンアップするとIssueが自動で閉じる
最後に、Node.jsのバージョンを上げると、Issueが自動でクローズされることを確認します。この章のバージョンアップは人が手作業で行います。
dnf版(web-old)をNode.js 22に上げる
Node.js 22は、endoflife.date によると、EoLは2027年4月30日です。しきい値の90日よりも先なので、起票の対象になりません。
web-old で、Node.js 22を入れて20を削除します。
sudo dnf install -y nodejs22
sudo dnf remove -y nodejs20
$ node --version
v22.23.2
$ rpm -qa | grep nodejs
nodejs22-docs-22.23.2-1.amzn2023.0.3.noarch
nodejs22-libs-22.23.2-1.amzn2023.0.3.x86_64
nodejs22-full-i18n-22.23.2-1.amzn2023.0.3.x86_64
nodejs22-npm-10.9.8-1.22.23.2.1.amzn2023.0.3.x86_64
nodejs22-22.23.2-1.amzn2023.0.3.x86_64
Issueが自動でクローズされる
Inventoryの収集を実行してから、Lambdaを実行します。
eol-gather-inventoryを「今すぐ適用」で実行し、Inventoryを更新する- Lambdaを実行する
web-old のIssueに自動でコメントが付き、クローズされました。


コメントの内容は、「(日付)のインベントリで検出されなくなったため、自動でクローズします。」です。web-nvm のIssueは、Node.js 20のままなので、開いたままです。
nvm版(web-nvm)も同じように閉じる
web-nvm も、nvmでNode.js 22に入れ替えます。
nvm install 22
nvm alias default 22
nvm uninstall 20
カスタムインベントリは、スクリプトが、30分ごとにJSONを書き直します。反映を急ぐ場合は、次の順番で実行しました。
eol-nvm-custom-inventoryを「今すぐ適用」で実行する(JSONを更新)eol-gather-inventoryを「今すぐ適用」で実行する(Inventoryに取り込む)- Lambdaを実行する
この順番を逆にすると、古いJSONを取り込む可能性があります。
Lambdaの実行後、web-nvm のIssueもクローズされました。


手動管理と比べて何が変わるか
冒頭で触れたExcel台帳での手動管理と比べて、今回の仕組みで何が変わるかをまとめます。
| 観点 | 手動管理(Excel台帳) | 今回の仕組み | 今回の検証 |
|---|---|---|---|
| 棚卸し | サーバーにログインして、バージョンを確認し、台帳に転記する | SSM Inventoryが自動で収集する。ログインも転記も不要 | 確認できた(nvm版は、補完が必要) |
| 台帳と実態のずれ | 手入力なので、更新漏れや入力ミスで実態とずれる | 実機から収集するので実態に近い | 考察 |
| EoLの把握 | 担当者がEoL日を調べて、台帳と照らし合わせる | endoflife.dateで自動判定し、近づくと起票される | 確認できた |
| 見落とし | 台帳を見る人が気づかないと、期限が過ぎる | 条件に合うものは、Issueとして起票される | 確認できた(検知できる範囲は、次の章を参照) |
| 対応状況の追跡 | 台帳に別途記録する | Issueで開いているか閉じたかが分かる。更新すると自動で閉じる | 確認できた |
| 属人化 | 台帳を更新する人に、作業が集中する | 仕組みが動くので特定の人に依存しない | 考察 |
注意点・制約
今回の検証で考えられる注意点を、対策の案とあわせてまとめます。
※「→」右側に考えられる対策を書きましたが、まだ試していない案です。
-
nvm以外の入れ方は検知できない可能性があります。コンテナの中のNode.jsは検出されません。
→ 入れ方で検知できないケースはスクリプトの探索パスを増やして補えるはずです。コンテナはイメージのスキャンなど別の仕組みが必要かと思われます。 -
EoL日が未確定のバージョンは、判定の対象になりません。endoflife.dateにEoL日が登録されていないバージョンは、起票されません。
→ 「EoL日が未確定」として、別のラベルで通知すると、見落としを防げるかもしれません。 -
Inventoryの更新は、収集の間隔(今回は30分)の分だけ遅れます。バージョンアップ直後は、Issueがすぐには閉じません。
→ 収集の間隔を短くするか、バージョンアップの作業手順に、「収集を実行する」を含めます。 -
endoflife.dateは外部サービスです。停止やAPIの仕様変更の影響を受けます。
→ 取得に失敗したときに通知する仕組みを入れます。 -
インスタンスを削除したときのIssueの扱いは、確認していません。Inventoryから消えると「検出されなくなった」と判断され、Issueが閉じる可能性があります。「対応が完了した」場合と「インスタンスがなくなった」場合を区別できません。
→ クローズするときに、インスタンスが存在しているかを確認して、クローズの理由をコメントで分けます。
まとめ
今回はEC2上にインストールしたソフトウェアのEoLを、SSM Inventory、Lambda、endoflife.date、GitHub Issueで検知して追跡する仕組みを作りました。実装してみて、次のことが分かりました。
- 棚卸し、期限の確認、対応状況の追跡という手作業を、AWSのサービスの組み合わせと少量のコードで置き換えられました。
- SSM Inventoryは、パッケージ管理に登録されたものを収集します。nvmで入れたNode.jsは拾われずカスタムインベントリで補う必要がありました。
- Issueの起票から、バージョンアップ後の自動クローズまでをつなげると、台帳を見に行かなくても、対応が必要なものと、対応済みのものが分かります。
この記事がEoL管理の参考になれば幸いです!







