SSM InventoryでEC2にインストールされたソフトウェアのEOL管理を自動化してみた

SSM InventoryでEC2にインストールされたソフトウェアのEOL管理を自動化してみた

ソフトウェアのEoL検知をAWS Systems ManagerとLambdaで自動化してみました。 期限切れ間近のものをGitHub Issueで自動追跡できるようになります。
2026.10.26

こんにちは。運用イノベーション部の小形です。

以前Azure基盤の運用保守を担当していた時に、VMにインストールされたソフトウェアのEoL(サポート終了)を、Excelの台帳で下記のような手順で手動管理していたことがありました。

  1. サーバーにログインしてバージョンを調べる
  2. 紙のメモにバージョンを書く
  3. Excelの台帳に転記する
  4. EoL日と照らし合わせる

この手順には次のような課題を感じていました。

  • 棚卸しのたびに申請や承認など手続きが発生する
  • 手入力なので更新漏れや入力ミスで台帳と実態がずれる
  • 通知の仕組みもないので台帳を見る人が気づかないとEoLを見落とす
  • 台帳を更新する特定の人に、作業が集中する(実際属人化していた)

そこで今回はこの棚卸しと期限の確認を仕組みにしてみました。AWS Systems Managerの機能の1つであるSSM InventoryでEC2上のNode.jsを収集し、endoflife.date のAPIでEoL日を判定して、EoLを迎えた(または迎えそうな)ものをGitHub Issueとして起票します。バージョンを上げると、次回の実行でIssueが自動でクローズされます。

なお、自動化したのは「検知」と「Issueでの追跡」までで、バージョンアップ自体は人が行います。

今回作った仕組みの全体像

今回作った仕組みの構成はこちらです。

eol_064.png

EC2上のNode.jsのバージョンをSSM Inventoryで収集し、毎日EventBridge SchedulerでLambdaを起動して確認します。EoLを迎えた、または迎えそうなものがあれば、GitHub Issueとして起票します。

処理の流れ

処理の流れは下記です。番号は構成図に記載の番号に対応しています。

  1. Systems ManagerのState ManagerでEC2から導入済みソフトウェアの情報を収集する。
  2. 収集した情報をSSM Inventoryに保存する。
  3. EventBridge Schedulerが指定した日時にLambdaを起動する。
  4. LambdaがSSM InventoryからNode.jsのバージョンを確認する。
  5. Lambdaが endoflife.date のAPIからEoL日を取得して期限を判定する。
  6. EoLが近い、または期限切れのものをGitHub Issueとして起票する。重複して起票しないよう、起票状態はDynamoDBに記録する。
  7. 運用担当者がIssueを確認する。
  8. 運用担当者がバージョンアップする。次回の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台とも「オンライン」になることを確認しました。

eol_005.png

作成したリソース

今回の検証で作成したリソースの一覧です。

種類 名前 用途
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 が表示されました。

eol_024.png

nvmで入れたNode.js(web-nvm)

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

eol_025.png

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確認結果↓

eol_009.png

web-nvmのrpm確認結果↓

eol_014.png

次の章でこの検知漏れを補います。

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"}]}

eol_030.png

このJSONは、Custom Inventory を有効にしたInventoryの収集で、SSM Inventoryに取り込まれます。次の収集を待たずに確認したい場合は、収集の関連付け(eol-gather-inventory)を「今すぐ適用」で実行します。

フリートマネージャーで、インベントリタイプを Custom:NodeJS に切り替えると、nvm版のNode.jsが表示されました。

eol_032.png

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": []
}

eol_040.png

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

eol_041.png

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

eol_039.png

eol_038.png

もう一度、Lambdaを実行しても、Issueは増えませんでした。opened が空になり、重複して起票されないことを確認できました。

定期実行する

ここまでは、Lambdaを手動で実行してきました。EventBridge Schedulerで、定期実行できるようにします。

スケジュールの設定

EventBridge Schedulerで、Lambdaを呼び出すスケジュールを作成しました。

項目 値
スケジュールのパターン 定期的なスケジュール(cronベース)
タイムゾーン Asia/Tokyo
ターゲット AWS Lambda の Invoke(eol-tracker)
ペイロード {}
実行ロール このスケジュールの新しいロールを作成

eol_046.png

実際の運用では、「毎日決まった時刻」で実行します。今回は動作確認のため、cronを作成の約5分後に実行される時刻にしました。

Schedulerから起動できたか確認する

設定した時刻のあと、Lambdaのロググループ(/aws/lambda/eol-tracker)に、新しいログストリームが作成されました。

eol_048.png

ログの出力は、次のとおりです。

{"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を実行します。

  1. eol-gather-inventory を「今すぐ適用」で実行し、Inventoryを更新する
  2. Lambdaを実行する

web-old のIssueに自動でコメントが付き、クローズされました。

eol_057.png

eol_059.png

コメントの内容は、「(日付)のインベントリで検出されなくなったため、自動でクローズします。」です。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を書き直します。反映を急ぐ場合は、次の順番で実行しました。

  1. eol-nvm-custom-inventory を「今すぐ適用」で実行する(JSONを更新)
  2. eol-gather-inventory を「今すぐ適用」で実行する(Inventoryに取り込む)
  3. Lambdaを実行する

この順番を逆にすると、古いJSONを取り込む可能性があります。

Lambdaの実行後、web-nvm のIssueもクローズされました。

eol_062.png

eol_063.png

手動管理と比べて何が変わるか

冒頭で触れた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管理の参考になれば幸いです!

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事