LAMP 構成のシステムが抱えていた問題を Amazon API Gateway + AWS Lambda のサーバレス構成にして解消した話

f:id:okashoi:20200306174651p:plain

ウィルゲートのアーキテクト兼技術広報の岡田(@okashoi)です。

今からおよそ 1 年前に取り組んだ、社内システムをリニューアルによってサーバレス化した事例についての紹介と、1 年経過したところのふりかえりや所感を書きたいと思います。

システムリニューアルの背景

社内向けのシステムなので詳細は述べられませんが、対象システムの概要は以下のとおりです。

  • 別のシステムから利用される想定の Web API を提供している
    • ウィルゲートのほとんどすべてのサービスに関わる基幹システム
  • 2013 年から稼働している
  • LAMP 構成
    • Memcache やファイルで管理されているデータもある
    • 言語は PHP でフレームワークは無し

このシステムは 2013 年から稼働しており、ユースケースも取り巻く環境も、作られた当初の想定とは大きく異なっていました。 その結果、以下のような問題を抱えていました。

利用量の増加に対してスケールしにくい

リニューアル前のサーバ構成図は以下のとおりです。

f:id:okashoi:20200304185809p:plain
リニューアル前の構成図

この構成では API 利用量増加に対してスケールするためには、新しくにサーバを追加する必要があります。 管理するサーバが増加することはそのまま運用コストの増加につながります。

サーバリソースの利用効率が悪い

対象システムは 1 日の中で時間帯によって利用量が変動する、という特性を持っていました。 サーバの性能設計は 1 日のピークにあわせていたため、それ以外の時間帯はリソースを持て余す状態になっていました。

エラーが発生した場合の原因究明が難しい

サーバ構成とは直接関係ありませんが、ログを分析する仕組みがなかったため、エラーが発生したとしても大量のログから該当の行にたどり着くだけでも一苦労でした。 また、仮にたどり着けたとしても、簡素な情報しか出力していなかったため、ログからは事象の全容を把握できないこともありました。

その結果「本番環境の Web API をパラメータを変えながら何度も叩き、その結果から原因の箇所を推測する」という職人芸じみた調査方法が横行し、強く属人化している状態でした。

リニューアルプロジェクト発足

対象システムの利用量は数年のうちに急速に伸びていくことが予想されていたため、特にスケーラビリティとサーバリソース利用効率の 2 点は喫緊の課題となっていました。

これらに加え、当初の設計思想が失われ意図の分からなくなった古い実装も多く抱えていたこともあり、アーキテクチャを含む抜本的なシステムリニューアルに乗り出すことを決定しました。

目的は「スケーラビリティ向上」 「コスト削減」 「信頼性向上」

このリニューアルプロジェクトでは以下の 3 点を主たる目的として置きました。

  • スケーラビリティ向上
  • コスト削減
    • 運用コスト
    • サーバコスト
  • システム信頼性の向上
    • 障害の起きにくさ
    • 障害からの回復しやすさ

メンバー3 名でおよそ半年にわたるプロジェクト

期間は 2018 年 10 月から 2019 年 3 月までの約半年間で、大まかにスケジュールは以下のように引きました。

期間 内容
2018 年 10 月 技術検証
2018 年 11 月〜 2019 年 2 月 実装およびテスト期間
2019 年 3 月〜 2019 年 4 月 システム移行期間

プロジェクトメンバーは私と、当時新卒 1 年目だったエンジニア 2 名の計 3 名でした。 それぞれの役割は以下のとおりです。

メンバー 役割
岡田 プロジェクトリーダー的な役割を担いつつ、主に設計や実装方針の決定、コードレビュー、テストを担当。
H 主に実装を担当。プロジェクト後半には実装上の設計にも参加。対象システムについて理解が深い。
T 主に実装とインフラを担当。プロジェクト後半には実装上の設計にも参加。

目的へのアプローチ

Amazon API Gateway + AWS Lambda によるサーバレスアーキテクチャの採用

プロジェクトの目的を鑑みつつ、以下の観点から相性が良さそうだったのでサーバレスアーキテクチャの採用を決定しました。

  • 必要なときに必要な分のリソースを使う、という形でサーバコストを削減できる
  • サーバを所有しなくて済むため、運用コストも削減できる
  • 対象システムのひとつひとつの機能はそこまで複雑な処理ではないため、それぞれを関数として切り出しやすい

ウィルゲートではクラウドベンダとして AWS を利用することが多いため、Amazon API Gateway と AWS Lambda を利用してサーバレスアーキテクチャを実現することにしました。

aws.amazon.com

aws.amazon.com

Amazon Elasticsearch Service を用いたログの可視化と運用を考えたログ設計

以前から開発室としてログの分析について検証をすすめており、このプロジェクトの発足を機に AWS が提供するフルマネージドな Elasticsearch サービスである Amazon Elasticsearch Service を使うことにしました。

aws.amazon.com

Amazon Elasticsearch Service では、ログの可視化を行う Kibana もあわせて利用することができます。

www.elastic.co

肝心のログにつていは、リニューアル前のシステムを運用してきた経験から「どんな情報をログから得られると有益か」ということがわかっていました。 そのため、その「有益な情報」を得られることを意識したログ設計を行いました。

プロジェクトでの取り組み

機能の洗い出し

リニューアル対象のシステムのユーザは他でもない我々エンジニア自身だったため、機能の洗い出しにはさほど苦労はしませんでした。 一方で存在意義のわからなかった機能は、各所にヒアリングのうえ削除することにしました。

Go 言語 + レイヤードアーキテクチャを採用

ウィルゲートではサーバサイドの言語として PHP をメインに利用しています。

しかしプロジェクトが発足した 2018 年 10 月当時、AWS Lambda は PHP をサポートしていませんでした*1。 そこで、ウィルゲートの導入実績が生まれつつあり、また私個人もプライベート開発で触れていた Go 言語を使うことを決めました。

また、実装レベルの設計にはレイヤードアーキテクチャを採用しました。 この選択には以下の理由がありました。

  • 前述のとおり自分たち自身がシステムのユーザであったため、はじめからドメイン知識がある程度明確になっていたこと
  • 実装を「サーバレスアーキテクチャ」のような特定の技術に強く依存させたくなかったこと
    • ドメイン層を分離して、ドメイン知識をシステムの詳細に依存させないような実装にできた
  • 社内の Go 言語に関する知見がまだ少なく、このプロジェクトではじめて Go 言語に触れるメンバーもいたこと
    • 習熟度に合わせてどのレイヤーから実装を任せるかを制御し、徐々にタスクの抽象度を上げていくことができた

詳細な説明やパッケージ構成に踏み込むと長くなってしまうため、ここについては別途記事を書きたいと思います。

なお、2018 年開催のGo(Un)Confernce(Goあんこ)LT大会 5kgにて LT もしたのでスライドも参考にしてください。

www.slideshare.net

データストアには Amazon DynamoDB を選択

保持すべきデータの性質や AWS Lambda との相性*2を考えてデータストアには Amazon DynamoDB を利用することを決めました。

テーブル定義の際には公式のベストプラクティスを熟読し、それに倣うことを重視しました。

最終的にリニューアル後の構成は次のようになりました。

f:id:okashoi:20200304185918p:plain
最終的な構成図

ふりかえり

プロジェクトは、おおむね計画どおりに遂行することができました。

f:id:okashoi:20200228172603p:plain
リニューアル前のシステムの削除日が決定したときの弊社 slack の様子

リニューアルからそろそろ 1 年経過しようとしていますが、その間にも取り巻く環境は変化しており、その中で分かってきたこともあります。

以下、このリニューアルプロジェクトの取り組みを通してのふりかえりを書いていきます。

プロジェクトの目的を達成できた

上述したアーキテクチャの刷新により「スケーラビリティ」「コスト」「信頼性」の 3 点すべてを改善することができました。

信頼性向上については、サーバレスアーキテクチャの採用だけでなく、後述の 2 つの要素も貢献しました。

Go 言語の性質と CI を併用してコストを下げつつ品質を向上できた

静的型付け + コンパイル型言語である Go 言語を選択し、CI でビルド+テストを実行するようにしたことで「ビルドが通っている」という事実を味方につけられるようになりました。

また、Go 言語には公式の linter(gofmt)も備わっているため、上記とあわせてプルリクエストで細かいミスのチェックに精神をすり減らす必要がなくなり、精神的/時間的なコストが削減されました。

その結果、実装上のミスによる障害はほぼなくなり、障害の頻度を減らすことができました。

自動テストに失敗している状態の Bitbucket Pipelines の画面
テストや Lint を自動で行うことでミスを事前に検知できる

f:id:okashoi:20200302164019p:plain
失敗した CI は slack に通知されるようになっている

ログの可視化によりエラー発生時の調査や対応が大幅に楽になった

Kibana によりエラーの件数と内訳の時系列推移が可視化されるようになりました。 これによって根拠を持って議論や修正ができるようになり、当初の期待以上に対応の精度とスピードが上がりました。

f:id:okashoi:20200228175553p:plain
Kinbana を活用してエラーの発生状況を把握している様子

サーバレスアーキテクチャでは従来とは異なる観点で監視を考える必要がある

サーバを所持しなくなったことで、プロセスの死活やリソースの利用状況を監視する必要はなくなりました。

一方で、思わぬ要因*3によって総実行時間が増加し、結果一時的にコストが増加してしまう、ということがありました。

また、制限に引っかかることで機能の提供ができなくなる、ということも起こり得ます。 例えば AWS Lambda では関数の同時実行数に上限があり、これを超えた場合はエラーになってしまいます。

AWS Lambda の制限 - AWS Lambda

このように、従来のリソース監視とは違う観点での監視を設計する必要があります。

Amazon Elasticsearch Service のコストが想定以上に膨れ上がってしまった

もともと検証という側面が強かった Amazon Elasticsearch Service の導入については、事前にコストを充分に検証していませんでした。

対象システムのログは 1 日を通して出力されており、結果 Amazon Elasticsearch Service のコストが想定以上に膨れ上がってしまいました。 これは本来のプロジェクトの目的であったコスト削減とは相反しています。

ログの分析や可視化の効力は、今回の検証によって身を以て知ることができました。 なので、現在ではフルマネージドでない代替の手段に切り替えることでさらなるコスト削減を図っています。

開発環境の構築に AWS SAM CLI や DynamoDB Local と Docker を組合わせると辛い

今回、開発環境で Amazon API Gateway + AWS Lambda の挙動を再現するために、AWS 公式が提供する AWS SAM CLI を使いました。

github.com

また、DynamoDB の挙動も再現するため DynamoDB Local を使いました。

DynamoDB ローカル (ダウンロード可能バージョン) のセットアップ - Amazon DynamoDB

もともと開発環境の構築には Docker を使っていたこともあって、Docker Compose を使ってこれらを一括で扱えるようにしたのですが、以下の理由から辛い思いをすることになりました。

  • AWS SAM CLI 自体が Docker を動かすため、 Docker on Docker 的なことをしないといけない
  • DynamoDB Local + Docker の組み合わせでデータの永続化させようとしたが、うまく動かなかった
    • 設定の問題だと思われるがうまく解消できず、都度データを入れる必要が出てきた
  • AWS SAM CLI による API Gateway は Docker Compose のネットワークの外に立ち上がる
    • DynamoDB Local とデータをやり取りする際に、設定に一捻り加える必要がある

対策としてはもちろん、AWS SAM CLI と DynamoDB Local の実行を Docker に頼らずにホストマシン上で行う、という方法も考えられます。

ですが実装のアーキテクチャにレイヤードアーキテクチャを採用していることを活かし、開発環境ではそもそも AWS SAM CLI や DynamoDB Local を使わない、という解決策が取れるのではないかと考えています。

つまり、開発環境ではシンプルな HTTP サーバを立てて、データ操作も DynamoDB を意識する必要がないスタブに置き換えられるのでは、という考えです。

これが実現できれば、動作やビルド時間が速くなるといった副次効果も期待できます。

f:id:okashoi:20200306160203p:plain
開発環境では AWS SAM CLI や DynamoDB Local を使わない案

この構成では開発環境と本番(相当)環境で最終的なプログラムが異なる、というデメリットがあります。 しかし、その差分の動作確認はテスト環境にて行えばよいと考えています。

まとめ - これからも改善は続いていく

以上が今からおよそ 1 年前に取り組んだリニューアルプロジェクトの概要と得た学びです。

古いシステムが抱えていた問題を解消しつつ、ウィルゲート的に新たな取り組みを多く盛り込めたプロジェクトであり、事業と技術の両面で非常に意義のあったプロジェクトになりました。 もちろん、リニューアルしたシステムも完璧なシステムであるわけでなく、また取り巻く環境も変わり続けるため、絶えず改善をしていく必要があります。

これからも解決すべき課題に着目し、適切な手法を選びながら改善を続けていきます。 そして、その知見もまた発信していきたいと思います。

*1:現在ではカスタムランタイムを使うことで任意のプログラミング言語を使うことができます。参考)https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-custom.html

*2:現在ではAWSのサービスの進歩によって、AWS Lambda と Amazon RDS の組み合わせ必ずしもアンチパターンとは言えなくなっています。

*3:必ずエラーが再現する状況で無制限にリトライしていた、など。