20年近く続いているレガシーシステムをAWSに移行しました

こんにちは!アドテクノロジーDivに所属している呉です。

直近では20年近く運用してる弊社の広告系サービスをAWS移行にしました。

今回はその中で苦労・工夫した点をご紹介したいと思います。

はじめに

20年近く運営している広告サービスなのですが、
40-50台程度のサーバがオンプレミスで構成されております。

エンジニアの方では日々の開発・運用を頑張っており、
プロダクトの機能拡大や安定運用に勤めております。

それでも技術負債など目立ち始めており、
さまざまな点で事業運営に影響を与えているため、
クラウドマイグレーションを通して少しでもレガシー要素を脱去するように舵を取りました。

主なレガシー要素(弊社の場合)

  • テストが手動(品質担保にコストが掛かっている割にテストの質が人依存)
  • ステートフルで構築やメンテナンス作業が職人技
  • アプリもミドルウェアは古い
  • セキュリティ周りは上流のネットワーク機器でカバー
  • など

移行戦略

移行難易度、リソース、チーム生産性向上などを色々熟慮した結果、
下記のような移行戦略を考えました。

  • アプリケーションは特にレガシーでクラウドリフト形式でアンパイをとる
  • サーバ数も多いので、IaC(Ansible、Terraform)の活用前提で移行する
  • ミドルウェアはクラウドシフト形式でモダン化する
  • 一気に移行するのではなく、ロール単位で部分的にクラウドに移行する

工夫・苦労したポイント

AWS基本設計のモダン化

弊社の広告サービスが複数存在しており、そのアーキテクチャも似てる特性から、
AWS基本設計(VPC、ネットワーク、セキュリティなど)の考え方を統一することで、
横展開が容易となり、全体品質の向上と移行リードタイム短縮の二点を期待できます。

VPC、ネットワーク

ネットワーク設計に気をつけているポイント

  • Public Subnetに入れるサーバを最低限にしているため、IPアドレス数を少なめに
  • ALBがトラフィックによってIPアドレスが動的に増減するのでLB専用Subnetを独立させる
  • Private Subnet(ApplicationとMiddleware)はサービスコアで使われるためにIPアドレス数を多めに
  • 今後の拡張性やアプリアーキテクチャ変更(ECSやEKS等)できるようにApplication Subnetの予備確保

セキュリティグループ

セキュリティグループに関してはFunctional Firewallパターンの考え方を採用し、
弊社の運用体制に合うようにアレンジしました。

引用元: CDP:Functional Firewallパターン - AWS-CloudDesignPattern

  • 基本的にInboundはアクセス要件を満たすように制御
  • Outboundは全て許可
  • インスタンスはdefault+機能毎のセキュリティグループを付与
  • Source は基本セキュリティグループを指定

セキュリティグループ詳細設計の例

セキュリティグループ 概要 備考
production-default-sg 全てのインスタンスに付与するデフォのセキュリティグループ production-bastion-sgからsshアクセスを許可
production-bastion-sg 踏み台サーバ用セキュリティグループ 特定のIPからしかssh許可しない
production-lb-sg ロードバランサ用セキュリティグループ 443ポートしか公開しない
production-app-sg アプリ用セキュリティグループ production-lb-sgから特定のポートへのアクセスを許可
production-middleware-sg ミドルウェア用セキュリティグループ production-app-sgから特定のポートへのアクセスを許可
production-vpn-sg オンプレミス接続用セキュリティグループ 一時的なもの、移行が終わったらインスタンスから随時取り外す

品質担保

ロール単位で部分的にクラウドに移行することもあり、テスト工数が膨大に見込めているのと、
ほぼ手動テストでカバーしましたが、コストと品質担保の限界にも課題が残っております。

そこで、テストを楽にできる仕組みの必要性がある考えているので、クラウド化する前に、
その仕組みを整えるように改善を優先させました。

移行前後のAB環境にそれぞれ同じデータを流すことで、
AB環境のDB間に差異がないかを確認できるツールになります。

仕組みの考え方が非常にシンプルですが、
EmbulkとGoReplayを活用して実現しています。

意識したポイントはこちら

  • カバレッジ向上のために本番データを活用する
    • Embulkで機密情報をマスキングした本番DBデータを複製する
    • GoReplayを活用し、webサーバの本番リクエストを複製する
  • テストデータの中身が最新の状態に保てるように、定期的にデータを自動更新する
  • 移行前後のAB環境にそれぞれ同じデータを流すことでその結果(DBのデータ状態)に差分の確認
  • 本番と分離した環境でテストを実施する

IaC化はがんばりました

今まで主にメンテナンス効率化の用途で活用してきたAnsibleですが、
クラウド移行で新規構築系のユースケースが追加されることにより、
Ansibleディレクトリの構成的にどうやって共存するかはわからないままコードを書きはじめました。

初期はベストプラクティスが見えないまま作りながら運用しているので、
クラウド化が進むことにつれコードも肥大化し、現場が混乱しかなり苦労しました。
そのためディレクトリ構成を見直すための議論を何度も重ね、コードのリプレイスも数多く実施しました。

今はこのような構成で落ち着いております。

├── group_vars
│   ├── production  # 環境ごとの変数
│   ├── test        # 環境ごとの変数
│   ├── all.yml     # 共通の変数
│   └── tag_role_xxx.yml  # グループごとの変数
│
├── host_vars
│   └── xxx.xxx.xxx.xxx   # ホストごとの変数
│
├── inventories       # inventory
│   ├── local_verify  # 環境かつグループやホストごとの変数
│   ├── production    # 環境かつグループやホストごとの変数
│   └── test          # 環境かつグループやホストごとの変数
│
├── xxx_configure.yml # 運用や設定関連をまとめたPlayBook
├── xxx_install.yml   # 初期構築などにroot権限が必要なPlayBook
│
└── roles
    ├── nginx.yml         # ミドルウェアのインストール
    ├── datadog.yml       # ミドルウェアのインストール
    └── web_balancer.yml  # サーバロールごとの設定
        ├── templates     # 各サーバに配置するファイル
        │   └── nginx.conf.j2
        ├── tasks         # 各サーバ上で実行するyamlファイル
        │   └── main.yml
        └── vars          # roleごとの変数
            └── main.yml

詳細説明は割愛させていただきますが、
意図的に用途別でplaybookを分離するようにしています。

  • xxx_install.yml <= 初期構築、または特権ユーザーの権限に依存しているものを集約する e.g: インストール作業
  • xxx_configure.yml <= サービスの運用やメンテナンス用途を集約する e.g: 設定ファイル更新

構築が終わって運用フェーズに入って、
特権ユーザーに依存されることなくサービスのメンテナンスや運用ができるねらいがありました。

おわりに

レガシーシステムのAWS移行はとても時間がかかり、
AWS知識やドメイン知識が必要な作業も少なからずありましたが、
クラウド化することでアーキテクチャやインフラ的な変化だけではなく、
そのプロダクト開発を支える周辺技術の刷新やチームの文化にも良い影響を与えるようになり、
より事業開発に集中できる環境として生まれ変わっているような気がしています。

まだまだクラウド化されていないサービスも存在してるのと、
クラウドネイティブ化されていないコンポーネントもあるので、
これからも引き続きクラウド化に向けた対応をチームで進めて行きます。

最後までお読みいただき有難うございました。