freeeの開発情報ポータルサイト

Rubyがマジョリティな会社でC#を使ってAWS Lambdaの本番運用を開始した話

こんにちは!freeeでエンジニアをしている @toshi0607 です。アイコンよろしくnyanchuと呼ばれてい ます。

Microsoft PlatformというチームでC#、WPF、Xamarinなどを使ってデスクトップアプリを開発しています。

この記事はfreee develpers Advent Calendar 2017の21日目です。

デスクトップアプリから送信するログファイルの処理をAWS Lambdaを使って行うようになりました。

この記事では導入の経緯や工夫したことについて紹介します。

段階的導入

最終的にLambdaを導入することを目指しつつ、ストレージへのログファイルのアップロードを非同期化することから始まりました。

まずはアーキテクチャの変遷をご覧ください。

Phase0

f:id:s_toshi0607:20171212230919p:plain

アプリからAPIサーバに複数のログをzipで固めて送信しています。

最初zipの中身をAPIサーバで1ファイルずつAmazon S3にアップロードしてからアプリにレスポンスを返していました。

そのため、ファイル数によってはタイムアウトしてしまうことがありました。

クライアント、サーバ両面から様々なアプローチを行いましたが、zip展開処理の非同期化がもっとも効果的でした。

Phase1

f:id:s_toshi0607:20171212230949p:plain

既にRailsサーバーでの一部処理でResqueを利用していたため、当面Resqueで様子を見ることにしました。

ただ、下記の理由からよりスケーラブルなサービスとして切り出したいという思いがありました。

  • Resqueは弊社の色々な機能での非同期処理に利用されており、優先度をつけながら運用している
  • アプリからに限らずログファイル展開で活用する未来がなんとなく見えていた

Phase2

f:id:s_toshi0607:20171212231007p:plain

11月末にこのアーキテクチャで本番運用を開始しました。

メインタスクではなかったので、合間時間や開発合宿などで開発してきましたがとても楽しかったです!

zipの非同期展開処理ができるのは変わりませんが、誤解を恐れずに言うとイベント駆動でスケーラブルなアーキテクチャになりました。

S3にzipファイルをアップロードするイベントでLambdaを起動させています。

Resqueでの運用にまつわる悩みもいくつか解消できていたりします。

はい。

なぜC#(.NET Core)か?

実はインフラ周りだけではなく、サービスでLambdaをnode.jsで運用してたりします。 Real World Serverless

それでもC#で書こうとしたのには理由があります。

メンテナンス

僕が所属しているチームはMicrosoft Platformチームです。

普段C#でクライアントアプリを開発しているので、メンテナンスやレビューの観点から極めて親和性が高いと考えました。

Visual Studio

Windows

強い。AWS Toolkit for Visual StudioをインストールすればVisual Studio上でデプロイ可能。

Lambda用のプロジェクトテンプレートを使用すればローカル実行もテスト(xUnit)を介して可能です。

環境構築にサーバーレスなフレームワークを使わずよしなにやってくれます。楽です。

Mac

Windows版ほどのサポートはありませんが、Visual Studio for Macでもローカルデバッグ環境を楽に構築することができます。

  • Visual Studio for Mac(エディションは注意)
  • .NET Core 1 系(現在VS4Macインストール時に2系がインストールされる)
  • xUnit.NET 2 testing framework support(拡張機能から有効化)

を使用することで、xUnitをFunctoionのドライバとしてデバッグすることができます。

弊社ではMac使いが多いため、Mac向けのLambda環境構築や.NET Coreの出自など詳しめにドキュメントを準備しました。

いざ本番運用

.NET周りのサポートが手厚いLambdaですが、心配なことがありました。

実際本番で運用されている情報はほとんどなく、リリースしたらどんなことが起こるのだろう…?というものです。

しかし、実行環境がどうであれやることは変わらないはず。基本に忠実に次のようなテストや仕組みで準備を行いました。

リトライ

処理に失敗したときはAWS側で自動的に2回リトライしてくれます。

完全に委ねました。

通知

リトライに2回失敗したとき、エンジニアはそれを知る必要があります。

これもAWS側で準備されている仕組みですが、次の図のような構成にしました。

f:id:s_toshi0607:20171219204803p:plain

2回失敗時のDLQとしてAmazon Simple Notification Service(SNS)を選択しました。

通知先はシンプルにメールです。

Lambdaのトリガーになったイベントそのものがつぎのような形式で通知されます。

{"Records":[{"eventVersion":"2.0","eventSource":"aws:s3","awsRegion":"us-east-1","eventTime":"2017-12-22","eventName":"ObjectCreated:Put","userIdentity":{"principalId":"AWS:xxx"},"requestParameters":{"sourceIPAddress":"xx.xx.xx.xx"},"responseElements":{"x-amz-request-id":"1234567","x-amz-id-2":"xxxxx"},"s3":{"s3SchemaVersion":"1.0","configurationId":"ZipUploadedEvent","bucket":{"name":"xxxxx","ownerIdentity":{"principalId":"xxxxx"},"arn":"arn:aws:s3:::xxxxx"},"object":{"key":"xx/xx/54321-xxxx.zip","size":12345,"eTag":"abcde","sequencer":"12345"}}}]}

監視 / ログ

リトライに失敗したときには通知された内容をもとにスタックトレースが見たいです。

CloudWatch Logsには通知内のx-amz-request-idやzipのファイル名に含めたログのID(上述のログでは54321)を吐き出し、検索できるようにしました。

移行 / 切り戻し

もし大量エラーが起これば手段を変えて処理を継続する必要があります。

Lambdaの処理の有効・無効はLambdaのポータルでアップロードイベントの発生の有効・無効で制御できます。

それと同様、元々のResque処理のコードも残し、Redisへのenqueueの有効・無効も管理画面で切り替えることができるようになっています。

代替処理

代替処理でもResqueを使用します。

RedisにenqueueするためだけのRailsタスクを作成し、ログのIDとzipファイルのパスを渡せばいつでもzipの展開処理が可能です。

段階的に移行を行った際の資産を有効活用できています。

ステージング環境での検証

色々と備えたものの、本番環境に出す前に本番に近い環境でのテストはしたいものです。

本番の疑似環境にリリースし、数日間運用してみました。

その結果、次のようなエラーに未然に対処することができました。

一時ディスク容量オーバー

実行環境の/tmpは512MBまで使用することができます。

ただし、同一のコンテナは(できる限り)再利用されます。

今回は/tmp下に展開したzipファイルの中身を置いていたため、タイミングによっては容量をオーバーしてしまっていました。

実行時に/tmp下にファイルがあれば消すようにしました。

容量0のファイルのアップロードでこける

S3のAWS SDKでファイルをアップロードするとき、容量0のファイルのアップロードに失敗したため、容量のチェック処理を入れました。

CI

いくらローカルで簡単にデプロイ環境まで構築できたとしても、個人の環境に依存するのは避けたいところです。

そこでCIサービスとしてVisual Studio Team Servicesを活用しました。

2017年の8月にAWS Tools for Visual Studio Team Servicesが発表され、Lambdaについてもビルドパイプラインが数分で構築できるようになりました。

f:id:s_toshi0607:20171212231622p:plain

ビルド・デプロイの設定はたったのこれだけです。

もともと設定項目少ないのはありますが…!

まとめ

以上を経て今のところは安定稼働しています。

f:id:s_toshi0607:20171221095246p:plain

たまにS3へのアップロードでこけますが(We encountered an internal error. Please try again.)、リトライの範囲内なのでまぁよしとしています。

これまでAWSはfreeeを支える重要なインフラとして部分的に触れてきたものの、特定のサービスをしっかり触ったことはありませんでした。

これを機に他のサービスにも親しんでいけたらと思います。

また、運用は開始したものの、つぎのような課題があります。

  • 通知
    • スタックトレースをいちいち探しに行かなくても、通知されたときに直接確認したい
  • CI
    • プルリクエストやタグでビルド・テスト・リリースをトリガーしたい
  • イベントの設定状況

今回のアーキテクチャを突き詰めても直接的にユーザに価値を届けることにはなりませんが、他に活かせるところを探しつつうまいこと付き合っていきます。

通知あたりはAWS Lambda Advent Calendar 2017の23日目に書けるようにがんばります。

さいごに

ここまで読んでいただきありがとうございました!

freeeではプロダクトを一緒に成長させてくれる仲間を募集しています。

興味を持ってくださった方はぜひお願いします!

www.wantedly.com

jobs.freee.co.jp

明日はCISO(Chief Information Security Officer:最高情報セキュリティ責任者)(長い)のtosaさんです。お楽しみに!