こんにちは、エンジニアリンググループの高橋(@tshohe1)です。
エムスリーでは今年の6月頃に認証付きのランディングページ(以下LP)を表示するサービスを Serverless Framework で新しく構築しました。本記事では、サービスの仕組みや、構築時に得られた知見を簡単にまとめたいと思います!
構築の目的
"レガシーシステムからの脱却" というのが一番大きい目的です。
これまでエムスリーの一部チームでは単発のページ(LP等)を、複数のレガシーなシステムで管理していました。
どんなレガシーシステムかというと...
- 新しい環境ではビルドも困難なほど古いCMS
- 廃止予定の別システムに依存
- 動作環境がコード化されておらず、スノーフレークサーバ状態
- EOLを過ぎたOS
等々、利用継続が困難(+危険)且つメンテナンスコストがとても高い状況でした。また、ページの作成に際してエンジニア作業が発生することもあり、そういった作業工数を削減することも目的としてありました。
構成検討
ざっくりとした要件としては「社内の認証基盤による認証後に、適切なユーザーに有効なLPを返却する」というものでした。既存の機能以外にもいくつか細かい要件(細かい表示制御等)は増えたものの割と単純な機能だったので、Lambdaでもいけるんじゃないかと思い下記構成で検討を開始しました。
なぜLambdaかというと...
- 構築コストが低い
- オートスケールする動作環境の構築コストが低い
- ランニングコストが低い
- 施策によって負荷の変動が大きいと思われるので従量課金が良さそう
というようなメリットがあると考えていたからです。
Serverless Framework
API Gateway + Lambdaを簡単にデプロイするための手段はいくつかありますが、プラグインやテンプレート/情報量の多さからServerless Frameworkを使うことにしました。
当初はエッジサーバーで処理したいと考えていたのでLambda@Edgeの利用を考えていましたがVPC内でなくてはならない弊社環境の諸々の理由からVPC Lambdaを使った構成となりました。
またランタイムについてはコールドスタート/ウォーム時両方で安定した速度が出る "Go 1.x" を採用しています(Serverless Frameworkのテンプレートは aws-go-mod を利用)
DynamoDB
単一テーブルで完結しており特にRDBMSである必要性がないことや、負荷の変動が多いことからDynamoDBを採用しました。 またオンデマンドキャパシティを利用することでキャパシティの見積もりコストやキャパシティ変更などの運用コストが抑えられるというのも大きな理由としてありました。
課題と対処
Serverless Framework での開発には下記のような課題もありました。
API GatewayのProxy先切り替え
共通処理を実施している一部のjsでは、クロスドメインからの接続に対応していないAPIを呼び出すため、接続先のホストからオンプレのサービスへProxyさせる必要がありました。
一応下記のようにProxy設定も簡単に設定出来るのですが、API GatewayがVPC外のサービスであるため、Route53のPrivate Hosted Zoneを使ってQA環境のときだけ接続先を切り替える、といったことが困難になりました(他のサービスはこの方法が多いです)*1
hoge-proxy: handler: bin/hoge-function # handlerの指定が必須となっているためdummy値の記載が必要 package: exclude: - "bin/*" events: - http: path: hoge/{proxy+} method: any integration: http-proxy request: uri: https://hogehoge.example/hoge/{proxy} parameters: paths: proxy: true
対応としてはLambda Function側でProxyさせる等があるとは思いますが、QA環境だけで発生する問題ということもありコストをケチるために社内から接続するときだけ社内の開発環境のProxyを経由するようにしました。
デプロイの安全性
sls deployでデプロイする場合はTerraformのplan相当のDry-runがないので、あの安心感に慣れていると少し本番適用が怖く感じることがありました*2。そこでまずはインフラレイヤーとアプリケーションレイヤーでStackを分割し、個別にmakeコマンドで適用できるようにしました。
. ├── Makefile ... ├── application-stack │ ├── ... │ ├── functions (各functionを格納) │ └── serverless.yml (API Gateway, Lambda設定のみ記載) └── infra-stack └── serverless.yml (その他AWSコンポーネント設定のみ記載)
これにより気軽にFunctionだけをデプロイを出来るようになりました。また、Functionの更新だけであれば強い権限を付与する必要がないため、CI/CDツールで適用を行うハードルも下がりました。
そしてインフラレイヤーの変更時は sls package
でCFnテンプレートだけ出力し、CloudFornationのChangeSetで事前に確認するといった運用にすればより安全に適用できると思います。
良かった点
良かった点は当初の目論見通りかなと思っています。
- 環境構築が容易
- キャパシティ計算や運用に要するコストが抑えられる
- (トラフィックがそこまで多くない間は)ランニングコストが抑えられる
他にも下記のような点も良かったかなと思います。
- 確認すべき情報はCloudWatchのMetricsにほとんど集まっているのでDatadogでの可視化が容易
- 今回は構成が単純すぎたため無効化しましたがX-Ray連携なども出来るようです
残念だった点
悪かった点とまでは行きませんが、ちょっと残念だった点はいくつかありました。
- アクセス数が安定していない場合はコールドスタートが発生しないように
serverless-plugin-warmup
プラグイン等で定期的にwarmupする必要があったこと- 安定したアクセスがあるときも実行してしまうので僅かではありますが、ランニングコストが上昇
- コールドスタートだと1秒オーバーしてしまうのでSLOを満たすために必要だった
- VPC Lambdaのコールドスタート性能が上がったという話もあるので、今は大丈夫なのかも?
- Lambda@EdgeのIPアドレスが固定できないこと
- これができていれば使いたかった
まとめ
いくつか課題には直面したものの、Serverless でほとんどメンテナンスフリーな環境を構築することが出来ました。Serverless Framework は開発が活発で新しいアップデートも色々とあるようなので今後も追っていきたいと思っています。
We are hiring
エムスリーでは新しい技術を使ってレガシーシステムのリニューアルをゴリゴリ推進していけるエンジニアやインフラ周りのモダン化を行ってくれるSREを大募集しています! ご興味のある方は是非下記リンクから問い合わせ下さい!