【3年間の取り組み】レガシーシステム刷新の苦労と成果

# はじめに

こんにちは、もんすんです。

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

レガシーシステムの保守・刷新は、多くのITエンジニアが避けて通れない課題です。

今回は、私と私のチームが、3年間かけて、レガシーシステムを<font color="red">レガシーから脱却するために取り組んだ内容</font>について紹介していこうと思います。
直面した課題と、その解決の過程で得られた知見、苦労話をお伝えします。

# 刷新プロジェクトにおける取り組み
## 3年前のレガシーシステム

レガシーシステムは、時代を超えて動き続ける反面、多くの課題を抱えがちです。
私のチームのケースでは、3年前に外部ベンダからの保守契約を終了し、内製化に踏み切りました。

その時点でのシステムの状況と課題を以下に整理します。

### システム概要

本システムは、10年以上前に外部ベンダが開発したWebアプリケーション(Java)です。  
以下のような特徴と問題を抱えていました。

- **混在する中国語**  
    設計書やソースコードのコメントに、**中国語**が残されていました。
    このシステムは、多重下請け構造の基で開発された際、**オフショア開発**を取り入れていたためそのような状態だったようです。
    英語であればまだ辛うじて、チームメンバーも読むことができたと思いますが、流石に可読性に問題がありました。
    日本語に翻訳された設計書・コメントもところどころ怪しい部分があり、仕様を正確に把握するのは困難でした。
    
- **オンプレからAWSへ移行済みだが整備不足**  
    外部ベンダの保守期間中に、オンプレミス環境からAWSへの移行が実施されていました。
    しかし、移行後も設計書やドキュメントの整備がなされておらず、システム全体の理解が難しい状況でした。
    特に、ネットワーク構成図もなく、どのようにインフラを構築したのかも不明でした。
    
- **テストコード・CI/CDが皆無**  
    システムの品質保証は、ほぼ「目視」と「経験」に頼る状況でした。
    コードの変更時には、リリース前にテストを完全に手作業で行っていました。
    リリース自体も人力で、ビルド・デプロイ操作する必要がありました。
    
### 具体的な課題💢

数ヶ月間の内製化プロジェクトを実施し、なんとか内製化を完遂することができました。
その対応後、大きな問題がいくつか浮き彫りになりました。

その例をいくつか紹介します。

#### 1. セキュリティ上のリスク

アプリサーバやバッチサーバはAWSのEC2インスタンス上で稼働していました。
これらのEC2インスタンスは、**パブリックサブネットに配置**されていました。
パブリックサブネットに配置されたEC2インスタンスは、<font color="red">インターネットから直接アクセス可能</font>な状態となります。
この構成では、セキュリティインシデントのリスクが高く、早急な改善が必要と認識されていました。

#### 2. リリース運用の非効率性

オンプレ時代の設計がそのまま引き継がれていたため、セッション管理がサーバ依存(**スティッキーセッション**)になっていました。
このため、サーバの入れ替え時にログイン中のユーザーが強制的にログアウトされるリスクがありました。
その影響で、リリース作業は深夜帯にしか実施できない状況が続いていました。
この頃は、リリース作業メンバーの作業負荷を配慮して、同じ人が連続してリリース作業を行わないように、スケジュール管理を徹底していました。

#### 3. 環境差異の影響

開発者は各自のPCに直接JDKをインストールし、ローカル環境でアプリケーションを実行していました。
チームメンバーは、そのようなローカル環境で、機能改善を実施していました。
この方式では、環境設定の差異が原因で、ローカル開発環境、動作検証環境、本番環境とで挙動が一致しない問題が頻発していました。
結果として、開発効率やリリース後のトラブル対応に多くの時間が割かれる状況でした。

## 課題解決に向けた3年間の取り組み💪💪

内製化から3年が経過した今、上記課題をどのように解決し、システムを改善していったのか。
それについて、具体的な取り組みを以下にまとめます。

### 1. ネットワーク構成の見直し

#### 取り組みのプロセス
1. **現行ネットワーク構成の把握**  
    最初のステップとして、既存のネットワーク構成を詳細に調査しました。
    - **AWSリソースの棚卸し**  
        使用中のAWS内のリソースを整理しました。
        例として、VPC、サブネット、セキュリティグループ、ルートテーブル、NAT Gatewayなどの設定を確認しました。
    - **通信フローの可視化**
        外部サービスや社内システムとの通信経路を洗い出しました。
        既存の設計と齟齬がないかを確認のため、動作検証サーバからそれぞれの通信経路における疎通確認も実施しました。
2. **新しいネットワーク構成の設計**  
    ベストプラクティスに基づき、以下の設計変更を行いました。
    - EC2をプライベートサブネットに移行し、プライベートサブネットからのインターネットアクセスをNAT Gateway経由へ
    - セキュリティグループを最小権限のポリシーに再設定し、インバウンド通信を必要最小限に制限
        - ここで、もともとのベンダのネットワークからのこのシステムへのSSHが可能な状態であったものをブロックしました。かなりセキュリティリスクの高い状態にあったと思われます。
    - 管理アクセスは、`AWS Systems Manager`のセッションマネージャーを使用する構成に変更し、直接SSH接続を廃止
3. **段階的な適用と検証**
    - **動作検証環境での適用**  
        まず動作検証環境で新しい構成を試験導入し、動作確認を実施しました。
    - **影響範囲の特定**  
        一部の外部サーバとの通信に関して、<font color="red">詳細が仕様書に記載されておらず、動作不全</font>が発生してしまいました。
        ログとトラフィック分析を通じて原因を特定し、必要な通信を新しい設定に追加し、難を逃れました。
    - **本番環境への段階的移行**  
        夜間メンテナンスのタイミングで、本番環境に対する変更を段階的に適用しました。

#### 取り組みの成果

1. **セキュリティの向上**  
    EC2が直接外部からアクセスされるリスクを排除することに成功しました。
    セキュリティグループの見直しにより、不必要な通信を完全に遮断しました。
    
2. **ネットワークの透明性向上**  
    新たに作成したネットワーク図により、システム全体の通信フローが明確化されました。
    そのおかげで、チーム内での共有が容易になりました。
    
#### 教訓
この取り組みを通じて、以下の教訓を得ましたので、共有します。

- **通信フローの可視化が重要**  
    設計の段階で通信経路を明確にしておくことで、後続のトラブルを大幅に減らせます。
    実は元々ネットワーク構成図を作らず、その場のノリで、サブネット変更できると思っていました。
    しかし、何のための設定かよく理解できないものがいくつかあったため、ネットワーク構成図を先に作って良かったと感じています。
- **段階的な導入の重要性**  
    全てを一度に変更するのではなく、環境ごとに段階的に移行することでリスクを最小化できたなと思います。

### 2. セッション管理の刷新

#### 取り組みのプロセス

1. **AWS Elasticache(Redis)の採用**  
    Redisをセッションストレージとして使用する構成を採用しました。
    - RedisをマネージドサービスであるElasticacheで運用し、運用負荷を軽減
    - サーバ間でセッションを共有するためのミドルウェア(例: Spring Session)の導入
2. **システム改修**
    - アプリケーションコードを修正し、セッションデータをRedisに保存するよう変更
    - セッションタイムアウトや接続プールサイズなど、Redis運用におけるパラメータを最適化

#### 取り組みの成果

1. **深夜リリースの廃止**
    セッションがサーバ非依存になったことで、日中リリースが可能に！！🎉🎉
    これにより、運用チームの負担が大幅に軽減されました⭐
2. **可用性の向上**
    Redisの導入により、サーバのスケールアウト/スケールインが容易になり、システム全体の可用性が向上しました。
3. **UXの改善**
    サーバの入れ替えや障害時でも、ユーザーセッションが維持されるようになりました。
    これにより、システムの信頼性が高まりました。
    
### 3. CI/CDとコンテナ化

#### 取り組みのプロセス

1. **CI/CDパイプラインの構築**  
    GitHub Actions(GHA)を利用して、CI/CDを自動化しました。
	- テストコードを追加
		- テストについては[こちらの自動テスト導入の記事](https://qiita.com/phibi-soon/items/f74f1199591aefe0adf7)に記載していますので、見てもらえたらと思います。
	- コードのプルリクエスト時に自動テストと静的コード解析の追加
	- コードのマージ時に成果物のビルド・サーバーへのデプロイの自動化処理の追加
2. **DBスキーマ管理の導入**  
    Flywayを使用し、以下の仕組みを構築。
    - DBスキーマ変更のスクリプトをバージョン管理し、変更履歴を一元化
    - 現行の開発環境DBからローカル開発環境DB用にシードファイルを作成
    - デプロイ時にスキーマ変更を自動で適用するワークフローをGHAに追加
3. **コンテナ化とECSの活用**
    アプリケーションをDockerコンテナ化し、EC2からECSにデプロイする構成に変更しました。
    - 環境ごとの設定差異をDockerイメージ内に統一
    - GHAで自動ビルドとイメージのリポジトリプッシュ、ECSへのデプロイを実行できるようにワークフロー追加
    - リリース時のダウンタイムをゼロにBlue/Greenデプロイ方式を適用

#### 取り組み成果

1. **リリース作業の大幅な効率化**  
    手動操作を排除し、リリースにかかる時間を大幅に短縮しました。
    開発から本番環境へのデプロイが極めてスムーズになりました。
    人の手を介することがなくなったので、作業負荷が減少しました。
2. **障害リスクの低減**  
    自動テストと一貫した環境設定により、環境差異が原因の障害が激減しました。
    また、リリースで人の手を介することがなくなったので、人的ミスも無くなりました。
3. **開発速度の向上**  
    CI/CDとテスト自動化により、開発サイクルの高速化を実現しました。
    これにより、新機能開発に集中できるようになりました。
4. **チームの知見アップ**
   チームメンバーの多くは、これまでレガシーシステムに慣れていた一方で、Dockerを活用したモダンな開発手法については知識が不足していました。
   今回の取り組みを通じて、コンテナ技術やCI/CDの基礎を学び、実際の運用に応用するスキルを習得することができました。
   これにより、チーム全体の技術力が向上し、今後の開発プロジェクトにおいても活躍できる人材へと成長する契機となりました。
    
## 苦労話💦💦

### 設計資料の不足

最も大きな課題の一つは、上述した通り設計資料やコードコメントがほとんど整備されていない点でした。
さらに、一部の資料やコメントが中国語で記述されており、チーム内での解読や理解に大きな時間がかかりました。
また、使用されているライブラリが珍しいもので、Web検索して情報を調べるのですが、その情報すら中国語で書かれているものが多く、調査に苦労しました。
オフショアを利用する際は、**日本語から翻訳した際、意味が大きく変わってしまうことがないか**をしっかり考えてから伝えることが重要に感じました。

### 経験不足による技術的な壁

チームメンバーのほとんどがレガシーシステムに精通していた反面、<font color="red">クラウドやコンテナ技術、CI/CDなどのモダンな開発手法にはかなり不慣れ</font>でした。
特に、インフラ対応をしてくれたメンバーとアプリ開発メンバーとで、インフラ関連の知識の差が大きかったです。
アプリ開発メンバーが、AWS特有の設定やサービス構成を理解するのに時間がかかりました。

知識さを埋めるには、定期的なトレーニングやハンズオンセッションを通じて、必要な技術を学ぶ機会を設けることが大切です。
しかし、その勉強会などの時間を確保するのも大変です。

今回は、3年という期間内だったこともあり、改善を優先してしまいました。
まずはプロジェクト内で小さな成功体験を積み重ねることで、徐々に理解を深めることも効果的だったのではないかと感じています。
なので、もう少し時間をかけて進めるべきだったかもしれません。

### 経営層や他部署との調整

システム改善のための投資やリソースの確保について、経営層を説得する必要がありました。
[こちらの自動テスト導入の記事](https://qiita.com/phibi-soon/items/f74f1199591aefe0adf7)でも触れていますが、「**新機能開発を優先する方がいいのではないか**」という話は何度かありました。
それについては、長期的にリリース頻度の増加や品質向上に伴う障害率低下による**費用対効果を繰り返し説明**するなど、メリットを伝え続けることを意識しました。

また、開発チームや運用チームとの調整においては、技術的な用語や意図が伝わらず、何度も説明や交渉を繰り返す場面がありました。
部署間のコミュニケーションを円滑にするために、共通の目標を設定し、**定期的な情報共有**を行うことが一番効果的でした。
対面でのミーティングよりは定期的に**対面**で集まって会話することで、距離感を縮めることも良かったと感じています。

### モチベーションの維持

長期間にわたる改善プロジェクトの中で、成果が見えにくい段階ではメンバーのモチベーションが低下する場面もありました。
特に、エラーや課題が頻発する初期段階やインフラ構成が大きく変わるようなフェイズでは、プロジェクトに対する不安が強まりました。

モチベーション維持のためには、振り返りで**小さな改善でも積極的に共有**することを心がけました。
**達成感**をメンバーに感じてもらうことを意識したことが良かったなと思います。

振り返りだけでなく、定期的なミーティングでチーム全体で課題や進捗を共有することも重視しました。
これにより、プロジェクトの意義を再確認し、お互いをサポートし合いながら対応を進めることができました。

# 終わりに

レガシーシステムの刷新プロジェクトの経験は、チームとして成長する大きな原動力になったと感じています。

特に、CI/CDの導入の取り組みは、運用効率、開発スピードを飛躍的に向上させました。
しかし、同時に知識の差、チーム間・部署間の調整など、多くの課題にも直面しました。
これらの課題を一つずつ乗り越える中で得られた教訓は、自分自身にとって貴重な財産になりました。

レガシーシステムの保守や刷新は、困難が多く、一筋縄ではいかないものが大半だと思います。
それでも、チームが一丸となり、地道に改善を積み重ねることで必ず進歩が得られることを実感しました。

この記事が、同じようにレガシーシステムに向き合うエンジニアやプロジェクトマネージャーの方々に少しでも役立つ情報や励ましになれば幸いです！😄