📚

現場で使えるライブラリアップデートの進め方

2024/12/24に公開

ライブラリアップデートってついつい後回しになってしまいますよね。
私たちのチームのプロダクトは運用が始まって10年程なのですが、ライブラリアップデートは後回しにされがちで、普段開発していても、「ライブラリが古すぎてある日急にコードが動かなくなる」ということが定期的に起こってました。

ライブラリアップデートはなぜ必要なのか?

ライブラリアップデートが必要な理由

今の開発チームの状況をなんとかして変えたいという思いがあったので、それを実現するために、ライブラリアップデートがなぜ必要なのかを言語化してみました。

  1. 開発が止まるリスクを避けるため
  2. 脆弱性に対応するため
  3. 迅速に脆弱性に対応できるようにするため
  4. ライブラリの新機能を使うため

1. 開発が止まるリスクを避けるため

ライブラリが古いままだと、開発が想定外のトラブルで停止するリスクがあります。
実際に私のチームで起こっていたので、体感としてすぐ理解できました。
「昨日まで動いていたコードが動かなくなった」、「新しいライブラリを追加したくてもできない」等です。
新機能を開発するために工数を見積もっても、このような事態が起こると予定工数やスケジュールが崩れます。
そのため、工数見積もりやスケジュールの精度を上げたり、開発者体験向上のためにも、別工数でライブラリアップデートを定期的に実施して、開発が止まるリスクを避けるべきだと思いました。

2. 脆弱性に対応するため

ライブラリは常に新しい脆弱性が発見されています。
そのまま放置すれば、セキュリティリスクが高まり、システムが攻撃される可能性が大きくなります。
私のチームでは、ライブラリの脆弱性管理にdependabotを使っているのですが、結構な頻度で脆弱性の通知が発生するので、定期的なライブラリアップデートがいかに必要か実感しています。

3. 迅速に脆弱性に対応できるようにするため

2だけだと、「脆弱性対応が必要なライブラリだけアップデートすればいい」という考えになりがちですが、そうではありません。
他のライブラリが古いままだと、重大な脆弱性を修正したバージョンに依存関係が対応できず、結果として脆弱性対応が遅れることがあります。
そのため全てのライブラリを定期的に最新に近づける必要があります。

https://zenn.dev/yuitosato/articles/cad5ab93e852ab

4. ライブラリの新機能を使うため

ライブラリの新しいバージョンには、新機能や改善が含まれています。
これらを活用することで、より効率的で保守性の高いコードを書けるようになります。
場合によっては、パフォーマンスの向上も期待できます。

https://zenn.dev/yuitosato/articles/cad5ab93e852ab

脆弱性対応が遅れると発生するリスク

重大な脆弱性対応が遅れることがどのくらいリスクなのか、正直あまり分かってないので調べてみました。
過去には下記のようなインシデントが起こっています。

Equifax社のデータ漏洩(2017年)

米国の信用情報会社Equifaxは、Apache Strutsの脆弱性(CVE-2017-5638)を悪用され、約1.5億件の個人情報が漏洩しました。
原因は、サードパーティライブラリであるApache Strutsの脆弱性が公表されていたにもかかわらず、Equifaxが人為的ミスでパッチバージョンを適用しなかったためです。
このインシデントによって、多額の補償金が発生し、株価も暴落しました。

https://www.scientia-security.org/entry/2019/03/23/120805

https://www.itmedia.co.jp/news/articles/1907/23/news055.html

Log4Shell(2021年)

Javaのロギングライブラリ「Log4j」における重大な脆弱性(CVE-2021-44228)が発見され、世界中の多くのシステムやサービスに影響を与えました。

https://www.issoh.co.jp/tech/details/3713/

https://piyolog.hatenadiary.jp/entry/2021/12/13/045541

ライブラリアップデートどうやって進める?

上記理由を説明し、私たちのチームでも後回しにされてきたライブラリアップデートを実施することができました。
以下に実施手順をまとめます。

実施手順

プロダクトはPythonコードで書かれており、poetryを使っています。
そのため、poetryでの具体的な手順をまとめます。

  1. 全てのtestをpassさせて、warningsを0に近づける
  2. 使っていないライブラリの削除
  3. 使っていないコードと関連ライブラリの削除
  4. 最も古い&影響範囲の狭いライブラリのバージョンアップ
  5. その他のライブラリのバージョンアップ
  6. poetry update

1. 全てのtestをpassさせて、warningsを0に近づける

現状のプロダクトだと、testが全てpassせず、warningsが数万件出ている状態だったので、いったんこれらを解消しました。
failedやwarningsが0に近ければ近いほど、ライブラリアップデートの影響を早期に発見しやすくなります。

2. 使っていないライブラリの削除

poetryファイルに記載されているものの、コード上では使われていないライブラリがあったため削除しました。
コード上で使われていないことを確認するために deptry が便利です。

https://pypi.org/project/deptry/

poetry add deptry
poetry run deptry .

pyproject.toml: DEP002 'simplejson' defined as a dependency but not used in the codebase
pyproject.toml: DEP002 'ipython' defined as a dependency but not used in the codebase
pyproject.toml: DEP002 'deptry' defined as a dependency but not used in the codebase

こんな感じでコード上で使われていないライブラリを一発で洗い出してくれます。
※コード上では使われていなくても、デバッグ用などで使われている場合もあるので、削除の際は慎重に確認しましょう。

3. 使っていないコードと関連ライブラリの削除

コード上ではライブラリが使われていても、そもそもそのコード自体が使われていないということがあったので、そういったコードやライブラリもこれを機に削除しました。

4. 最も古い&影響範囲の狭いライブラリのバージョンアップ

いまだにライブラリのアップデートができていませんが、ようやくここでライブラリのアップデートです。
アップデートする際の順番は、バージョンが最も古いもの、かつ、影響範囲の狭い(使われている箇所が少ない)ライブラリを優先すべきだと思いました。
バージョンが新しいライブラリから先に上げようとすると、古いライブラリとの依存関係がなくてエラーになることが多かったためです。
また、使われている箇所が少ないライブラリは、3に繋がる可能性が高かったため優先しました。

アップデートの仕方は、上げたいライブラリを最新バージョンに書き換えて、

poetry update ライブラリ名

で、一個ずつ手動で上げていきました。
その後に下記を確認しました。

  • テストが全てpassすること
  • warningsが出ていないこと
  • リリースノートに記載された破壊的な変更点に影響ありそうなコードがないこと

5. その他のライブラリのバージョンアップ

4と同様の手順で全てのライブラリのバージョンを上げていきます。
pandasnumpy のアップデートに特に苦労したので、今後は高頻度でアップデートをしていきたいと思いました。

6. poetry update

最後に、依存関係全体を最新の状態にします。

poetry update

継続的に実施するための取り組み

ライブラリアップデートは1回やれば終わりではなく、継続的に実施していく必要があります。
しかし、そのための仕組みがないと継続的に実施することは難しいです。
そのため、下記を実施しました。

1. タスクと工数を可視化する

今回ほぼ全てのライブラリアップデートを実施したので、定期的に実施する場合、どのくらいの工数が必要なのかを可視化することができました。
また、poetryファイルやnpmファイルなど、ライブラリアップデートが必要なものをドキュメントにまとめることで、タスクの可視化もできました。

2. Dependabotの導入

Dependabotを導入することで下記を自動化することができました。

  • ライブラリの脆弱性検知の自動化
  • ライブラリの脆弱性アップデートPRの自動作成
  • ライブラリの脆弱性アップデートPRがパッチバージョンだった場合にオートマージする

詳細は長くなるので次の章でまとめます。

Dependabotでライブラリアップデートを一部自動化しよう

Dependabotとは?

Dependabotとは、GitHubが提供しているサービスで、ライブラリアップデートを一部自動化することができます。
機能は下記3つです。

1. Dependabot alerts

GitHubリポジトリのライブラリに脆弱性が発見された場合、アラート通知を行う

2. Dependabot security updates

GitHubリポジトリのライブラリに脆弱性が発見された場合、脆弱性に対応したバージョンに修正するPRを自動作成する

3. Dependabot version updates

GitHubリポジトリのライブラリが最新でない場合、最新のバージョンに修正するPRを自動作成する

https://zenn.dev/sumiren/articles/ffe6c0bd772718

設定方法

どの機能も無料で使えますが、導入するには設定が必要です。
GitHubのSettingsタブにある Code security で機能をオンにできます。

ライブラリの脆弱性アップデートPRがパッチバージョンだった場合にオートマージする

私たちのチームでは1の Dependabot alerts と2の Dependabot security updates を取り入れることにしました。
3の Dependabot version updates を取り入れなかった理由は、PRが溢れて管理コストが現状より増大すると考えたからです。
その代わり、ライブラリ全体のバージョンアップは、定期的に手動で実施する予定です。

1と2を取り入れたことで、今まで実施されていなかった脆弱性の検知を自動化することができました。
また、脆弱性のバージョンアップについてはDependabotがPRを自動作成してくれます。

もっと自動化できないかと思っていたところ、下記の記事を見つけました。
下記のコードを参考に、以下の条件を満たした場合、脆弱性のPRをオートマージするようにしました。

  • CIのテストが全てpassしていること。
  • パッチバージョンの更新であること(マイナー・メジャーバージョンは対象外)。

https://dev.classmethod.jp/articles/github-dependabot-auto-merge/

マイナーバージョンやメジャーバージョンは、Dependabotがやってくれない破壊的な変更点のコード修正を別途手動で実施する必要があるため、オートマージにするのは危険そうでした。

最後に

ライブラリアップデートをしながら、普段から不要なコードを削除することの重要性を身にしみて感じました。

参考記事

https://zenn.dev/yuitosato/articles/cad5ab93e852ab

https://zenn.dev/sumiren/articles/ffe6c0bd772718

https://dev.classmethod.jp/articles/github-dependabot-auto-merge/

https://github.com/pricing

https://docs.github.com/ja/code-security/dependabot/dependabot-alerts/about-dependabot-alerts

https://docs.github.com/ja/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates

https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates

ENECHANGE

Discussion