レガシーシステム刷新は冒険物語だと思う

この記事は [「レガシー」を保守したり、刷新したりするにあたり得られた知見・ノウハウ・苦労話 by Works Human Intelligence Advent Calendar 2024](https://qiita.com/advent-calendar/2024/works-hi-2) への投稿となります。

普段は[ネタ記事](https://qiita.com/water_resistant)ばかり書いていますが、今回は真面目な**長文**記事です。

※ 記載内容については上長から許可をいただいています。

## 私と会社について

- 会社員
    - 弊社9年目
- 役割
    - フルスタックエンジニアに近いポジション
- 最近の業務
    - システム保守の計画・実施
- システム構成
    - AWSをメインとしたクラウド環境

## 記事のテーマ

レガシーシステム（以下「レガシー」）の保守・刷新で得た知見や想いを綴ってます。
具体例として昨年実施したEC2の総入れ替えを中心に、ポエムな感じで記載しています。

## EC2総入れ替えの概要

昨年、利用中のOSサポート終了に伴い、以下の流れでEC2全台再構築・再配置を行いました。

1. 現状のEC2リスト化
1. 移行先OS選定・新規EC2配置
1. 各ライブラリ・ミドルウェアのインストール・設定
1. アプリケーション配置
1. 段階的な切り替え

**この記事では上記対応を話の軸にしています。**

<details><summary>【--- 目次 折りたたみ ---】</summary>

- [刷新活動で重要視する3タスク](#刷新活動で重要視する3タスク)
    1. [レガシーの現状を[正確に]理解する](#1-レガシーの現状を正確に理解する)
    1. [やることを決めて宣言する](#2-やることを決めて宣言する)
    1. [やらないことを決めて宣言する](#3-やらないことを決めて宣言する)
- [保守・刷新を通じて普段から意識していること](#保守刷新を通じて普段から意識していること)
    1. [名称・命名はしっかり考える](#名称命名はしっかり考える)
    1. [ドキュメント整備に全力を注ぎすぎない](#ドキュメント整備に全力を注ぎすぎない)
    1. [少しずつ慎重に。時には大胆に。](#少しずつ慎重に時には大胆に)
    1. [対応ログを残す](#対応ログを残す)
    1. [プランBを用意する](#プランbを用意する)
    1. [Before / After を記録する](#before--after-を記録する)
    1. [成果を説明して評価される](#成果を説明して評価される)
    1. [レガシーは単純な悪ではない](#レガシーは単純な悪ではない)
- [最後に個人的な感想](#最後に個人的な感想)
</details>


## 刷新活動で重要視する3タスク

実際には3つどころでないですが、文量を考えて3つに絞りました。

1. レガシーの現状を[正確に]理解する
2. やることを決めて宣言する
3. やらないことを決めて宣言する

組織の規模によっては活動を理解・支援してもらうことが最重要というケースもあります。
そういった事情もあるので、今回はなるべく組織規模に依存しない3項目に絞りました。

また、これらは**私が**大事だと思うだけで個人差があります。

### 1. レガシーの現状を「正確に」理解する

レガシーにはドキュメントが不十分な場合が大半なので、「考古学者」のような発掘作業が必要です。

[稀によくある](https://dic.nicovideo.jp/a/%E7%A8%80%E3%81%AB%E3%82%88%E3%81%8F%E3%81%82%E3%82%8B)状況ですが、今回のケースを一部抜粋します。

- AWSリソース管理状況
    - IaCとコンソール操作が混在
- サーバ上にのみ設定情報が存在
    - 各ミドルウェアの設定ファイル
    - サービスに利用する環境変数
- ライブラリ利用状況が複雑
    - パッケージマネージャーで導入
    - 担当者が手動ビルド
    - バージョンに差異

最近のサービスではコンテナ化等によりこれらの課題は大幅に減少します。
ただし、レガシーの場合は「考古学者」のように現状を発掘・把握する作業が必要です。

この**理解フェーズ**を突破すれば、半分は終わったようなものです。
当たり前ですが、**このフェーズが最も重要です**。

余談ですが私は普段、低レイヤーから理解することを心がけています。

#### ネットワーク構成を理解する

今回はコンソールから直接作成されたリソースが多いので、AWS CLIを活用しつつ、コンソールを見て**気合で**頑張りました。

また、並行して[システムアーキテクチャ図](https://aws.amazon.com/jp/builders-flash/202204/way-to-draw-architecture/)を作成しました。
図式化には[draw.io](https://www.drawio.com/)を利用しましたが、場合によっては[diagrams](https://diagrams.mingrammer.com/docs/getting-started/installation)などを使うこともあります。

なお、draw.ioの出力画像には[メタデータ部分にダイアグラム情報が含まれる](https://www.drawio.com/doc/faq/export-diagram)ため、再編集が可能です。
画像コミットして編集できるなんて便利ですね。

| 構成図1 (抜粋) | 構成図2 (抜粋) |
|:-:|:-:|
|![貼り付けた画像_2024_12_09_11_48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/8a13bc6a-d91c-e76a-bfe6-bbd7171fa90b.png)|![貼り付けた画像_2024_12_09_11_56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/0728f0d1-6d09-9ab5-f57f-3b61823463ec.png)|

ちなみに現在担当している案件では、さらに規模が大きかったため、[AWS WorkLoad](https://aws.amazon.com/jp/solutions/implementations/workload-discovery-on-aws/)を活用しました。

ただし設計が不適切だと出力結果をそのまま利用することは難しいです。
そのため中間データとして活用しつつ、**気合いで**補完しました。
それでも作業効率は大幅に向上したので、状況に応じて検討すると良いでしょう。

最終的にはそれを中間データにして**気合いで**頑張りました。

[適切に設計](https://docs.aws.amazon.com/ja_jp/wellarchitected/latest/framework/welcome.html)されている場合であれば自動運用も可能で、気合いに頼る必要はないかもしれません。

逆説的ですが、設計が適切かを確認するためにも一度ツールを試してみるのも良いでしょう。

なお、「気合い」という言葉を繰り返していますが、レガシー対応には考古学者のような情熱、読み解く力、そして忍耐が必要です。
中途半端な覚悟やスキルでは、ミイラ化してしまうかもしれません。

#### 各マシンの役割を理解する

各マシンで利用されているライブラリやパッケージ、設定内容を把握することが重要です。
今回は下記事情があったので、AWS CLIを活用しつつ、コンソールを見て**気合いで**頑張りました。(2度目)

- サーバ上に直接ファイルで設定が保存されている
- 環境変数がマシンごとに異なる
- cronが各マシン上で手動編集されている
- パッケージを通さずビルドされたライブラリが存在
- AWS Systems Managerの設定が不適切
- など

[AWS Systems Manager](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/what-is-systems-manager.html)を適切に設定していれば、気合いを用いずとも利用パッケージやバージョンが簡単に把握でき、負担が軽減します。

今回救いだったのは、過去の担当者が[Ansible](https://www.ansible.com/)を残していたことです。
不完全ながらも、設定の大枠を迅速に理解できました。
ただし、冪等性を保ちAnsibleを記述・保守し続けるのは大変です。
場合によってはコンテナ化などの計画を検討するのも良いでしょう。

最後に、設定の差異を洗い出し、必要な設定だけを抽出して理解完了です。

![貼り付けた画像_2024_AA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/6580576a-7c34-0b11-192c-d80ac2460ca8.png)

ここで重要なのが「 **決めつけないこと** 」です。
見出しに 「正確に」と強調した理由はここにあります。
思い込みや決めつけが原因で大事故につながることもあるため、以下のようなケースに注意が必要です。

- 開発ドキュメントに記載があるから大丈夫
- Ansibleに記述があるから間違いない
- 信頼できる社内エンジニアの発言だから安心
- マシン名称から役割が分かるはず

これらはすべて過信のリスクをはらんでいます。
自分で確認・観測・計測する姿勢が最も大切です。

#### モニタリング環境を確認・整備する

モニタリング環境の確認・整備は、刷新成否を判断するうえで必須事項です。
現状を把握し、不足があれば事前に整備しましょう。

また、情報を一箇所に集約することも重要です。
刷新直後に「どこを確認すれば問題ないか」を即座に分析できる状態を作ります。

弊社では [New Relic](https://newrelic.com/jp) を利用しているので、こちらにメトリクスやエラー情報を集約しました。

| マシンと実行プロセス | アプリケーションエラー |
|:-:|:-:|
|![貼り付けた画像_2024_12_09_12_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/8f8e3ec8-9141-bdb8-9322-bc6126614be8.png)|![貼り付けた画像_2024_12_09_12_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/73f13bd8-b013-e937-a7cd-778b49bbd15d.png)|

### 2. やることを決めて宣言する

現状を理解したら、次に「何をするか」を決めて**合意形成**を行います。

今回、会社側の要件はOSの更新のみでしたが、非機能要件を含め、このタイミングで実施すべき変更を提案しました。
特にマシンの再構築・再配置という大規模な変更はチャンスです。

また既存設定のそのまま移行が適切かどうかは検討が必要です。
次期バージョンで廃止予定の設定をそのまま引き継ぐべきではありません。
変更範囲を適切に抑え、原則として挙動を変えないことを前提に進めましょう。

##### 実施した主な作業

- OS更新
    - 移行先OSの選定・検証
    - バージョンアップ対応と廃止ライブラリの移行
    - パッケージ管理外ライブラリの設定・ビルド
- 責務分割
    - PHPのプロセス分離
        - mod_php → php-fpm 移行
    - Webサーバ流入制御を一部 [AWS WAF](https://aws.amazon.com/jp/waf/) に切り出し
- ネットワーク設定見直し
    - 不要なネットワークピアリングの解除
    - サブネット設計と公開範囲の見直し
- 各種チューニング
    - インスタンスタイプ変更
    - ミドルウェアのパラメータ調整

挙動変わってるよね？というツッコミは尤もです。
ただ「原則」と書いたように状況によっては挙動変更も必要です。
大事なのは変更範囲や影響について、関係者と明確に合意形成しておくことです。

そしてテストについても用意しておきましょう。
というより、**普段からテストを書いてCIに組み込んで**おきましょう。

### 3. やらないことを決めて宣言する

刷新の機会だからと、すべてを詰め込むのは避けるべきです。
変更が多すぎると問題発生時の**原因切り分けが困難**になり、不具合や障害を招くリスクも高まります。

そのため、明確な理由をもって「やらないこと」を決め、宣言することが「やること」と同じくらい重要です。

今回のケースでは以下を「やらないこと」としました。

- コンテナ化
- ログ転送基盤の変更

これらは刷新後に計画する方針とし、変更範囲を適切に絞りました。
（刷新後に計画が進まないリスクもありますが、それは別の話です）

また、「やるべきこと」「やらないこと」を宣言する際には、自己完結せず、関係者からの**合意・承認を得る**ことを忘れないようにしましょう。

## 保守・刷新を通じて普段から意識していること

刷新のタイミングだけでなく、普段の開発活動でも意識しておくことも多いです。

### 名称・命名はしっかり考える

名称は非常に重要です。
関数名、リソース名、設定名など、**あらゆる名称が**対象です。

一度運用に乗せると名称の変更は**極めて困難**です。
以下のような汎用的すぎる名称を付けそうなときは立ち止まってください。

- `common`
- `service`
- `admin`
- `getData`

今回のケースでも不要と思われる機能に「common-service-common」などがあり、削除時に大きなリスクと**恐怖**を伴いました。

この恐怖を乗り越えるのは**相当な気合いと覚悟**が必要になるので、触らぬ神に祟りなし、となって放置されがちです。

これらを回避するには、初めから適切な名称を付けることが重要です。

命名のコツを学ぶには、リーダブルコードなどを参考にすると良いでしょう。
また、CI/CDに命名ルールのチェックを組み込むのもおすすめです。

※参考: [リーダブルコードの要点整理と活用法をまとめた | Qiita](https://qiita.com/KNR109/items/3b14e2e8f89a33c0f959)
    
そして命名を補完するためにコメントで説明するケースもありますが、あまりおすすめしません。
コメントを記載するなら「何をするか」よりも、「なぜそれをしないのか」に焦点を当てるべきです。

名称案に悩んだ際は、CopilotやChatGPTなどを活用すると良い提案を得られます。
現代のツールは非常に便利なので、積極的に活用しましょう。

※参考: [「コメントは書くな」 | Qiita](https://qiita.com/hiro-hori/items/1cf28a6ac75db077c8f3) 

ただ個人開発や社内ツールなどは気分がアガる名称のほうがいい場合もあります。
ケースバイケースです。

### ドキュメント整備に全力を注ぎすぎない

大前提としてドキュメントは大事です。
あくまでドキュメント**作成自体が目的**にならないように、という意味です。

難解な実装や仕様を補う超大作を作るより、実装や仕様そのものをシンプルにする方が効果的です。 [禅](https://peps.python.org/pep-0020/)
これにより、保守・刷新活動が格段に楽になります。


またドキュメントは**保守性も意識**して作成しましょう。
特にフォーマットの選定が重要です。
画像のみや特殊フォーマットなど、編集が困難な形式は避けるべきです。

理想は、更新差分が分かる形式で管理することです。
ｺﾚｶﾞﾑｽﾞｶｼｲ

### 少しずつ慎重に。時には大胆に。

保守と刷新は陰と陽、表裏一体です。
少しずつ進めるべきか、一気に進めるべきかの判断に正解はありません。
チームで議論し、状況に応じた最適なアプローチを模索しましょう。

システム変更の適用タイミングも同様です。
「秘密兵器をいきなり実践投入して全撃破」などは映画の中だけです。
現実には、慎重かつ計画的なリリースが求められます。

リリース手法にはさまざまな選択肢があります。
適切な方法を選び、可能であれば普段から小さな試みを重ねて経験を積みましょう。
初めてのリリース方法は予想以上に緊張感を伴うものだからです。

※参考
[リリース手法多すぎﾜﾛﾀｧ B/G、カナリア、機能フラグ、ダークローンチ、A/Bテスト | Qiita](https://qiita.com/minorun365/items/3c92187cb251abbd92bc)

### 対応ログを残す

*「伝説のエンジニアがレガシーを一掃して去った。今では口伝が残るのみ。」*

…カッコいいですが、現実では困ります。
また、担当者が在籍していても、当時の詳細を忘れてしまうことは意外と多いものです。

そのため、対応時のログを残すことが重要です。
次回の刷新時やトラブル対応時に、担当者が見返して役立ちます。

普段から記録する習慣をつけると、作業のベストパターンが見えてきます。
さらに、IaCを活用すれば、それ自体が具体的なログとなり、楽しながら記録を残すことができます。


| 情報集約 (※ほぼモザイク) | コンソールはキャプチャで楽 |
|:-:|:-:|
| ![貼り付けた画像_2024_12_09_12_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/a2dbca9d-31b6-d830-4a9a-2a79b9d58ce1.png) |![貼り付けた画像_2024_12_09_12_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/c07c553d-03a3-e328-757d-18269571f73b.png)|

### プランBを用意する

レガシー刷新は、新プロダクトや機能開発と比べ、失敗時のダメージが大きい場合が多いです。
そのため、入念な検証を行ったとしても、次善策としてプランBを用意しておくことが重要です。
刷新の適用計画と撤退計画はセットで考えましょう。

また、いきなり完璧なプランBを用意するのは難しいため、普段の開発活動でも「リリースが失敗したらどうするか」を常に意識することが大切です。

### Before / After を記録する

刷新による影響を把握するため、結果を可能な限り**定量的に記録**しましょう。
正常に刷新できたか判断するうえでも重要です。
今回のケースでは、モニタリング環境を整備し、以下のような資料を作成しました。

![貼り付けた画像_2024_12_09_11_57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/089e9cef-8392-9da3-fe82-dcd359cc2b19.png)

今回の対応でパフォーマンス改善することはわかっていました。
上記画像のマーカー箇所前後で大きく変わっていることがパット見で分かる形になっているかと思います。

また他には、設定コード削減量やファイル数の削減なども記録しました。

これは保守・刷新活動に限らず、[LPO](https://kaizenplatform.com/contents/lpo)など普段の開発活動においても必須のプロセスです。

### 成果を説明して評価される

保守・刷新活動は外から見ると「**以前と何も変わらない**」と見えがちで、新プロダクトや機能開発と比べて評価されにくい側面があります。

ただこれを軽視すると、評価されない上に華々しさも薄い仕事となり、保守刷新を希望する人材が不足するネガティブスパイラルに陥る可能性があります。

また自身が評価されないだけでなく、続く後輩も評価されない風土になってしまうので踏ん張りましょう。

##### 評価を得るために私が意識してる点

- 成果を可能な限り定量化
    - コスト削減、稼働率向上、エラー発生率低下など
- 定量化が難しい部分は定性的に可視化
    - 図表やレポートを活用して直感的に伝える

今回は以下のような資料を用意しました。
（前項のBefore/After記録とも関連します）

| 設計観点 | コスト観点 |
|:-:|:-:|
|![貼り付けた画像_2024_12_09_12_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/1eb3bc7c-80e0-f180-0d3d-00f175123227.png)|![貼り付けた画像_2024_12_09_12_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6799/5fcf8c31-7d24-48c9-9458-972ff0a45237.png)|

### レガシーは単純な悪ではない

実はこれが**今回最も伝えたい**ことです。

レガシーは時に「オワコン」として扱われがちですが、**保守刷新が必要な時点で、それは価値を生み出し続けているシステム**です。
価値のないプロダクトなら保守刷新の必要もありません。
むしろ、事業にとって重要な部分ほどレガシー化する傾向があります。

(一方で過去の経験を活かしてしっかり設計した新プロダクトに限って、早々に破棄されることもあります。悲しいね。)

実際、今回刷新したシステムの大半は過去に私が作成した場所ではないです。

重要なのは、過去の担当者やプロダクトを批判するのではなく、どうすればいいか考えることです。
※偉そうに言ってますが、**非常に難しくて私自身できていない**こともあります

もし過去に対して文句を言いたくなるなら、まず当時の事業状況や背景を知る努力をしましょう。
その時代に自分がいたら、同じ選択をしていた可能性は十分にあります。
そして、同じ失敗を繰り返さないためにも、背景を理解することは大切です。

また、レガシーがなくなることもありません。
多かれ少なかれレガシーは生まれ続けます。そして誰であっても生み出します。
そういった意味でも、当時の実装者を憎まずの精神でいきたいですね。
経験年数が少ないとピンと来ないかもですが、それらは確実に自分に帰ってきます。

## 最後に個人的な感想

今回のマシン総入れ替えは諸事情により私単独の進行でした。
ただ**その間の事業施策全部は他メンバーにお願い**して、刷新へ集中できる体制を整えました。

大変な作業でしたが、少数で進めたからこそ判断や適用のスピードを保つことができました。
個人的には、保守はチームで、刷新は少人数で実施するのが効果的だと感じています。

そして私は `レガシー = 遺産` というフレーズだけで、刷新活動をやたら考古学に例えていますが、案外似ているところも多いと思います。

地図(ドキュメント)がない状態で現地(サーバ内)に向かい、危険な道(テストなし)を突き進み、謎の石碑(謎設定・コード)を解読して、宝(新システム)を手にして、無事に帰還(不具合0)しなくてはいけない。

むしろ、考古学と言うより[冒険物語](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%83%87%E3%82%A3%E3%83%BB%E3%82%B8%E3%83%A7%E3%83%BC%E3%83%B3%E3%82%BA_%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA)な気もしてます。

> 考古学が求めるものは事実だ。真理ではない。真理に興味がある者は哲学教室へ行け。失われた都市とか埋もれた宝は存在しない。地図の×印を掘って宝が出たためしはないのだ。
> ※ インディ・ジョーンズ シリーズより

未知のレガシー遺跡から無事脱出できたときの達成感は格別です。
新規開発(開拓)は人気領域ですが、遺跡探索もまた別の方向での楽しみがありますよ！
みなさんもぜひ「冒険者」として挑戦してみてください。