この記事は Serverless Advent Calendar 2018、JX通信社 Advent Calendar 2018の16日目です。
12月15日、Developers Boostにて「なぜサーバーレス『と』Dockerなのか 〜インフラ運用を最小化するサービス開発〜」というタイトルで発表させていただきました。本稿はその補足記事です。
※ 登壇資料中にもありますが、今回は サーバーレス=FaaS(AWS Lambda など)として扱います 。また、記事中の例示はすべて AWS Lambda のものです。
JX通信社の NewsDigest では、サーバーのデプロイ環境として、Docker クラスターやサーバーレスを活用 しています。お互い同列なデプロイ環境として考えて、ケースバイケースで使い分け ているような感じです。
サーバーレスや Docker の本番利用の事例も増えてきていると思いますが、サーバーレスかコンテナのどちらかしか使わないのではなく、どちらもメリット・デメリットある中で「どのように使い分けるのか」という判断が重要です。また、特にサーバーレスに関しては 「チームでシステム開発するには」 という視点を忘れずに持っておくべきだと考えています。
サーバーレスのチーム開発
チームでシステム開発をする際に、だいたい次のものは必要なのではないでしょうか。
- プログラムを書くこと(ここは当たり前ですね)
- 簡単にローカル環境が作れること
- 自動テスト
- 簡単にデプロイできること
- CI/CD
- 監視
サーバーレスを「ただのデプロイ先」として考えると当然、サーバーレスであっても、全く同じものが必要です。
サーバーレスに依存しないサーバーレスバッチ
サーバーレスを「ただのデプロイ先」として扱うには、アプリケーションの作り方も特定のプラットフォームに依存せず動くように工夫する必要があります。
まずは、次のようなコードを見てみましょう。次のプログラムは、CloudWatch Events で指定された text
を標準出力する Lambda 関数です。
def lambda_handler(event, context): # CloudWatch Eventにて、JSONパラメータを設定 {"text": "text to show"} print(event['text'])
当然ながら、このシステムは、Lambda 上でしかろくに動きません。このような場合、次のようにリファクタリングをします。
import argparse def show(text: str): print(text) def lambda_handler(event, context): # CloudWatch Eventにて、JSONパラメータを設定 {"text": "text to show"} show(event['text']) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('text') args = parser.parse_args() show(args.text)
こうすると、このシステムは 「サーバーレス環境でも動くだけの、ただの CLI プログラム」 として抽象化されます! ただの CLI プログラムなので、ただの CLI プログラムとして、テストをしたりすれば良いということになります。手元で検証するときも python main.py hogehoge
と実行すれば、ただの CLI プログラムとして適切に実行されます。
サーバーレスに依存しないサーバーレス API
上記の例はバッチのようなシステムの用途の想定ですが、API についても同様です。
awsgi や Zappa を使うと、普通の Python の API が AWS Lambda にデプロイできる API に早変わり します。次の例は、ただの Flask 製 API としても、サーバーレスな API としてもデプロイできます。
import awsgi from flask import Flask, jsonify app = Flask(__name__) @app.route('/') def index(): return jsonify(status=200, message='OK') def lambda_handler(event, context): return awsgi.response(app, event, context)
このような形であれば、テストも「ただの Flask API」としてテストすれば良いですし、環境構築等も同様です。
同様の「サーバーレスじゃない API をサーバーレスでも動くようにするライブラリ」は Node(express) や Go などでもあるみたいです。
これらのライブラリは、「今あるものをサーバーレスに移行する」というモチベーションが多いような気もしますが、個人的には新規プロジェクトでも積極的に使っています。
デメリットとしては、
- 余分なライブラリを介するので遅くなるかもしれない
- 「エンドポイントごとに関数をわける」みたいな設計にはならない
というのがあったりするので一長一短ですが、serverless フレームワークに乗せるにしても「プラットフォームに依存せず動く」という方針はキープしておくと良いように思います。
サーバーレスのテスト
あまり特別なことはしていませんが、サーバーレスではマネージドサービスが頻出するので、dynamodb-local や localstack のようなモックをよく活用しています。
ただしこのやり方は Twelve-Factor App の開発/本番一致に反していますし、モックサービスは完璧ではありません。「本物のマネージド・サービスを使うべきだ」という意見もあると思います(一長一短ですね)。
また、t_wada さんによる「外部に依存したコードもテストで駆動する」も参考になると思います(Alexaスキルをテスト駆動開発しているものです)。
サーバーレスなプロジェクトのローカル環境例
発表では docker-compose を使うとさらっと述べたのですが、サーバーレスなプロジェクトも、Docker クラスターへデプロイするプロジェクトも、基本的に docker-compose を使って環境構築がさらっとできるようになっています。JX 通信社のアクティブなリポジトリだとほぼ全て docker-compose.yml が用意されています。
例えば、次のように定義します。
version: '3.5' services: app: build: . command: python -m flask run --host 0.0.0.0 volumes: - .:/usr/src/app depends_on: - dynamodb dynamodb: image: amazon/dynamodb-local
こうしておくと、「ただの DynamoDB を使う一般的なプロジェクト」として扱うことができますし、サーバーレス環境にデプロイする際に必要な Liunx 下でのライブラリなども取れて便利です。dynamodb-admin みたいな、GUI のものも入れておくと便利です。
余談1:デプロイ
デプロイについては、
- ECS(Dockerクラスター):ecs-deploy
- Lambda:aws-cli
を使ってるケースが多いです。Apexも使ってましたが、最近は使ってないです。今から始めるならserverlessとかも良いと思います(参考:サーバーレスのメリット&本質を、AWS Lambdaを使って理解しよう)。
余談2:始め方
懇親会では「どうやって始めたの?」という質問も聞かれました。
サーバーレスもDockerクラスターも、まずは新規のプロジェクトで試しに本番運用してみて「良さそう」となってから横展開しました。サーバーレスは tech.jxpress.net でも1章割いて紹介したログ基盤が使い始めたプロジェクトです。
まとめ
伝えたかったのは、
- IaaS/VPS を直接運用している場合は、サーバーレス化/Docker化するとインフラ管理が楽になる
- 「サーバーレス vs コンテナ」じゃなくて、どっちも併用して使い分けたい
- サーバーレスであっても「チーム開発」の視点を持ちたい
- そのために プラットフォームに依存しないように作りましょう (本稿)
といったところです。
今回は貴重な登壇の機会をいただき、デブストオーガナイザーの近藤さん、スタディスト北野さん、ありがとうございました!
また、発表では拙いところもあったと思いますが、何かわからないことがあれば Twitter 上で @yamitzky までメンションを飛ばしていただければと思います。