サーバーレスのメリット&本質を、AWS Lambdaを使って理解しよう

「サーバーレス」はここ数年の技術トレンドの一つです。サーバーレスアーキテクチャを2年運用してきたJX通信社の小笠原みつき(yamitzky)さんが、そのメリットや実際の業務における考え方を、ハンズオンを交えながら解説します。

サーバーレスのメリット&本質を、AWS Lambdaを使って理解しよう

JX通信社の小笠原みつき@yamitzkyと申します。ニュース速報アプリ「NewsDigest」の事業統括をしているエンジニアです。

JX通信社では、人手のかかる「報道」という分野を機械化・自動化することをミッションの一つとして掲げており、技術選択においても、なるべく運用を自動化できる方法を選ぶよう心掛けています。その過程で「サーバーレス」という技術に出会い、2016年ごろから本番運用してきました。

今回の記事のゴールは、サーバーレスという技術が「どんなものであるか?」を理解することです。サーバー監視ツールの制作を通じて、特定のサービスだけにフォーカスせず、実際の業務で生かせる「考え方」の習得を目指します。

サーバーレスの沿革

以下の図は、Googleトレンドで「serverless」という言葉の人気度を示したものです。

1

Googleトレンドにおける「serverless」の人気度の動向

2015年の終わりごろからよく使われ始めたようですが、その勢いは未だ衰えていません。AWS(Amazon Web Services)やGCP(Google Cloud Platform)のようなクラウドサービスでも、サーバーレスを押し出した製品が次々に発表されていますし、日本でもServerlessconfというカンファレンスが開かれるなど、技術トレンドの一つとなっています。

「serverless」という単語自体が初めて使われたのは、2012年ごろと言われています。「Why The Future Of Software And Apps Is Serverless」という記事では、クラウドサービス普及によるサーバーとメモリコストの低下と、コンピューティング環境の変化について述べています。

The phrase “serverless” doesn’t mean servers are no longer involved. It simply means that developers no longer have to think that much about them. Computing resources get used as services without having to manage around physical capacities or limits.

Ken Fromm「Why The Future Of Software And Apps Is Serverless」(2012年10月15日、ReadWrite)より

その後、2014年にサーバーレスコンピューティングサービスの「AWS Lambda(以下、Lambda)」が発表されました。

2 AWS Lambda (サーバーレスでコードを実行・自動管理) | AWS

今となってはLambdaはサーバーレスの代表格ですが、当時のアナウンスブログでは「serverless」という言葉は使われず、「コードをクラウド上で実行する製品」という紹介がされていました。

Today we are launching a preview of AWS Lambda, a brand-new way to build and run applications in the cloud, one that lets you take advantage of your existing programming skills and your knowledge of AWS.

AWS News Blog「AWS Lambda ― Run Code in the Cloud」(2014年11月13日)より

2016年に入ると、AWS Lambdaに対抗するサービスとして、GoogleからCloud Functions、IBMからOpenWhisk、MicrosoftからAzure Functionsが立て続けに発表されています。

3 Cloud Functions - Event-driven Serverless Computing | Google Cloud
4 OpenWhisk サーバーレス・アーキテクチャー - IBM Bluemix
5 Azure Functions―サーバーレス アーキテクチャ | Microsoft Azure

もはやサーバーレスは、主要なクラウド事業者では基本的なサービスという位置づけになっていることが分かります。

6

サーバーレスの歴史

また、既存のクラウドサービスも、サーバーレスの文脈で語られるようになってきています。

例えば、GoogleのBigQueryはビッグデータの分析基盤を提供するフルマネージドサービスで、2011年ごろから存在しますが、2016年半ばになってから「BigQuery is serverless」と紹介されるようになっています。

サーバーレスの沿革を紐解くと、次のようなことが分かります。

  • サーバーレスのコンセプト自体は、必ずしもAWS Lambdaによって最初に実現されたわけではない
  • サーバーレスという言葉が誕生する以前から、サーバーレスなサービスが存在した
  • BigQueryのようなマネージドサービスも、サーバーレスと呼ばれる

サーバーレスとは何か

AWS LambdaやCloud Functionsのようなサービスは、PythonやGoなどで書かれたロジック=関数をクラウド上で実行するものです。処理実行の際に必要なインフラは、クラウド側が管理してくれます。関数の実行をマネージドにしたサービスとも言えるので、Function-as-a-Service(FaaS)と呼ばれます。

個人的には、FaaSは「狭義のサーバーレスの定義」だと思っています。

先ほどBigQueryもサーバーレスとして紹介されていることを述べましたが、BigQueryはPythonやGoなどのコードを動かすためのサービスではありません。その代わり、SQLで指定した集計処理をフルマネージド環境で実行してくれます。

BigQueryはFaaSではありませんが、サーバーレスなサービスとしてAWS Lambdaなどと次のような共通点があります。

  • フルマネージドであり、インフラを管理する必要がない
  • 関数やSQLなど、何らかの処理を実行してくれるサービスである
  • リクエストが発生したときにだけ、クラウド側で計算リソース(CPUやメモリ)を柔軟に確保する

サーバーレスを一言で定義するのは難しいのですが、私は、リクエストに応じて、必要なだけの計算リソースをほぼリアルタイムで確保するアーキテクチャという認識をしています。ただし、OpenFaaSのような「オープンソースのFaaS基盤」もあり、必ずしもクラウド上のマネージドサービスだけとは言えません。

本稿ではこれから実際にAWS Lambdaを触ってみますが、サーバーレスの重要なポイントであるリソース確保の柔軟性を理解するためにも「いつリソースが確保されているのか?」を意識してみましょう。

サーバーレスのメリットとデメリット

サーバーレスアーキテクチャの大きなメリットの一つは、リソース確保の柔軟性です。

サーバーレスなAPIでは、1つのAPIへのリクエストが「1回のLambda関数の実行」に該当します。そのため、各リクエストの処理に対して、最低128MBのメモリが確保されます。その場合、同時に100リクエストが発生したら、合計12.8GBのメモリが即座に確保されることになります。この際に重要なことは「各リクエストが確保したメモリ量で処理できるか?」であって、システム全体で確保すべきメモリに関してはあまり考える必要がありません。

逆に、1リクエストもないときにはメモリは確保されず、費用も発生しないので、ユーザー数の少ない新規のWebサービスなどでは安価な運用ができるでしょう。後ほど詳しく見ていきますが、これから作ってみる監視システムも、Lambdaの費用は1カ月で1円もかからないはずです。

さらに、マネージドサービスを組み合わせていけば運用コストも下げることができ、エンジニアはロジックに集中できます。

サーバーレスアーキテクチャには、デメリットもあります。

サーバーレスでは、基本的にはクラウド依存が前提になります。クラウド事業者の設定している上限を超えるような使い方はできませんし(Lambdaの場合、同時実行数などに制約がありスケールが難しい場合もあります)、まれにサーバーレス環境自体に障害が起きることもあります。

そのため、あらかじめ特定のサーバーレス環境に依存しない設計で作っておくことが重要です。本稿でも、ハンズオンの途中で「コードを良くする」工程を入れ、AWS Lambda以外の環境でも使える設計を目指します。

サーバー監視ツールを作ってみよう

それでは、実際にサーバーレスなシステムを作ってみて、その仕組みを理解しましょう。題材として、次のようなサーバー監視ツールをAWS Lambdaを使ってサーバーレスに作ってみます。

  • 1分間に1回、Webサービスへアクセスし、レスポンスタイムを監視する
  • 監視状況を、Amazon CloudWatch Metricsに保存する

今回は「Webサービスのレスポンスタイムの監視」という例ですが、「SQLを使ったデータベースの状態監視」や「ECサイトの販売状況の監視」や「天気データの監視」など、趣味でも実務でもいろいろと応用の効くシステムです。

全体のアーキテクチャーは、以下の図のようになります。

7

サーバーレスな監視のアーキテクチャー

3つのサービスはそれぞれ異なる役割を持ちます。

CloudWatch Events
イベントに基づいて、AWSのさまざまなサービスを起動させるマネージドサービスです。Lambdaを定期的に動かします
Lambda
マネージドなFaaSです。Webサービスを監視して、その結果をCloudWatch Metricsに書き込みます
CloudWatch Metrics
メトリクスを保存して表示するマネージドサービスです。監視結果を保存します

事前準備

一般的に、FaaSの関数をデプロイするには、次の3つの方法があります(使用するプログラミング言語やクラウドサービスによって多少の差はあります)

  • 管理画面上で直接コードを書く
  • 公式のツール(AWS CLIなど)を使ってデプロイする
  • 外部のツールを使ってデプロイする

今回は、Serverless Frameworkという外部のツールを使います。Serverless Frameworkは、AWS Lambdaだけでなく、GCPやMicrosoft Azureなど、いくつかのサービスに対応しています。

Serverless Frameworkのセットアップは、公式のクイックスタートを参照してください。

本稿は、2018年6月現在のAWSで動作検証しています。Pythonは3.6.1、Serverless Frameworkのバージョンは1.27.2です。

Lambda関数のデプロイ

それでは、実際にサーバーレス環境を触ってみましょう。まずは「service-watcher」という名前でプロジェクトを作ります。--templateの引数は、どのようなテンプレートでプロジェクトを初期化するかを指定したもので、aws-python3はランタイムをPython 3.6としたAWS Lambda向けのテンプレートです。

$ serverless create --template aws-python3 --path service-watcher
$ cd service-watcher

このプロジェクトには、「handler.py」と「serverless.yml」という2つのファイルが含まれています。handler.pyがソースコードです。ただのPythonスクリプトなので、python handler.pyと実行してみても、特にエラーなく終了します。

handler.pyを次のように編集してください。このコードは、example.comにアクセスして、レスポンスが返ってくるまでにかかった時間を表示するものです。

import datetime
import urllib.request

def hello(event, context):
    started_at = datetime.datetime.now()
    with urllib.request.urlopen("https://example.com"):
        ended_at = datetime.datetime.now()
        elapsed = (ended_at - started_at).total_seconds()
        return f'Time: {elapsed} seconds'

これをデプロイしてみましょう。

$ serverless deploy

AWS Lambdaの管理画面にログインすると、service-watcher-dev-helloという関数が作成されているはずです。Lambda関数の詳細を見ると、「関数コード」の項目の中にhandler.pyというファイルが存在していて、先ほど変更したコードがデプロイされていることが分かります。

8

デプロイされたLambda関数

Lambda関数の動作確認

試しに、管理画面上で「テスト」のボタンを押してLambda関数を実行してみてください。すると実行結果が表示され、サービスのレスポンスタイムの監視ができていることが分かります。

9

実行結果

課金期間の「100 ms」は、わずか100ミリ秒のみが課金対象となっていることを示しています。つまり、1回実行しても0.000000208円分の費用しか発生していないということです。

同様の監視ツールをEC2のようなIaaSで作るには、あらかじめインスタンスを借りておかないといけません。しかし、Lambdaの場合は必要なときに必要なリソースのみをほぼリアルタイムに確保できているため、Lambdaを動かしている間の100ミリ秒分だけ費用を支払えば良いことが分かります。

Lambda関数のテスト実行は、ターミナル上からもServerless Frameworkを使って行なうことができます。

$ serverless invoke -f hello
"Time: 0.056416 seconds"

ここまでで、Lambda関数の基本的なデプロイと動作確認が完了したことになります。このままでは手動でLambda関数を実行したときにしか監視ができませんので、定期的に実行されるようにしてみます。

Lambda関数を定期的に実行する

serverless.ymlを開いて、次のように変更してください。events:以下の部分が、今回の変更です。

functions:
  hello:
    handler: handler.hello
    events:
      - schedule: rate(1 minute)

再度、デプロイを行います。

$ serverless deploy

管理画面を確認してみると、左側にCloudWatch Eventsが追加され、スケジュール式のところに「rate(1 minute)」と表示されています。

10

CloudWatch Events

この左側の欄はAWS上では「トリガー」と呼ばれていますが、「どのようなイベントを起点として、Lambda関数が起動するか」を表しています。この場合は、「1分に1回」というイベントです。例えば、サーバーレスなAPIの場合、「API Gatewayへのリクエストが発生すること」がトリガーとなります。

監視結果を保存する

最後に、監視の結果をCloudWatch Metricsに書き込みます。次のようにhandler.pyを変更してください。

import datetime
import urllib.request
import boto3

def hello(event, context):
    cloudwatch = boto3.client('cloudwatch')
    started_at = datetime.datetime.now()
    with urllib.request.urlopen("https://example.com"):
        ended_at = datetime.datetime.now()
        elapsed = (ended_at - started_at).total_seconds()
        cloudwatch.put_metric_data(
            Namespace='Watcher',
            MetricData=[{
                'MetricName': 'ResponseTime',
                'Unit': 'Milliseconds',
                'Value': elapsed,
                'Dimensions': [
                    {'Name': 'URL', 'Value': 'https://example.com'}
                ]
            }]
        )

これをデプロイして、実行してみましょう。

$ serverless deploy
$ serverless invoke -f hello

すると、次のようなエラーが出て失敗します。

{
    "errorMessage": "An error occurred (AccessDenied) when calling the PutMetricData operation(略)",
    ...
}

これは、今デプロイしたAWS Lambdaが、CloudWatch Metricsに対して書き込みをする権限を持っていないために起きているエラーです。

AWSなどのクラウドサービスに慣れていないうちは、どうしてもクラウド特有の権限設定などで詰まったりすることもあるので要注意です。

serverless.ymlを変更して、Lambda関数にCloudWatch Metricsに書き込むための権限を与えます。iamRoleStatements:以下の行が今回の変更分です。

provider:
  name: aws
  runtime: python3.6
  iamRoleStatements:
    - Effect: "Allow"
      Action:
       - "cloudwatch:PutMetricData"
      Resource: "*"

Lambda関数のソースコード内のcloudwatch.put_metric_dataという処理に権限が足りていなかったので、cloudwatch:PutMetricDataの権限を許可しています。

再度、デプロイと実行をしてみましょう。

$ serverless deploy
$ serverless invoke -f hello

今度はエラーが表示されず、正常にCloudWatch Metricsへの書き込みが行われたはずです。

監視結果を確認する

続いて、監視の結果を確認してみましょう。

CloudWatch Metricsの管理画面を開き、メトリクス→Watcher→URL→ResponseTimeと選択してみてください。すると、以下のようにグラフが作られています。

11

CloudWatch Metrics

ここまでで、サーバーレスにWebサービスの監視を定期的に行い、結果を保存してグラフが見られるようになりました。

今回はServerless Frameworkを使ってシステムをデプロイしましたが、Lambda、CloudWatch Events、IAMの設定を、それぞれの管理画面から手動で行うこともできます。

サーバーレスなアラートへの発展

サーバーレスに監視するだけではなく、監視の結果、異常があればSlackへアラートを投げるといったシステムへ発展させることもできます。

12

サーバーレスなアラートの投稿

実際にJX通信社でも「DBに入ってくるレコード数が少なかったらアラートを投げる」や、「AWSの特定のサービスが期待通りに稼働していなかったらアラートを投げる」といった監視を同様のアーキテクチャーで行っています。

このシステムでは「監視」と「アラートを投げる」部分が疎結合になっているので、監視対象が増えても、アラートを投げる部分のLambda関数は1つだけで完結します。

また、この例のようにサーバーレスなシステムの設計を突き詰めていくと、マネージドサービスを組み合わせ、イベントドリブンで各サービスが発火していくような設計になります。

コードをより良くしてみる~ローカル環境での実行

今回作ったhandler.pyは、AWS Lambdaの環境で動くことしか想定していないコードです。これをローカル環境でも実行できるようにしてみましょう。

watcher.pyというファイルを作って、次のようにコードを書き換えてみます。

# watcher.py
import datetime
import urllib.request
import boto3

def watch(url):
    cloudwatch = boto3.client('cloudwatch')
    started_at = datetime.datetime.now()
    with urllib.request.urlopen(url):
        # 略

# `python watcher.py https://example.com`と実行できる
if __name__ == '__main__':
    import sys
    watch(sys.argv[1])
# handler.py
import wacher
# Lambdaはこの関数を呼び出す
def hello(event, context):
    watcher.watch("https://example.com")

この設計の場合、今まで通りLambda上でも動き、ローカル環境でpython watcher.py [url]することでも動作できます。今回は、Lambda関数を作った後にローカル環境で動くように変更しましたが、ローカルで動くCLIツールを、Lambdaでも動くようにラップするような設計です。

JX通信社では、サーバーレスなAPIを作る際も同様の戦略を取ることが多いです。FlaskやDjangoなどの一般的なウェブフレームワークで開発しておけば、awsgiのような「Lambda/API Gatewayで動くようにするためのラッパーライブラリ」を使ってサーバーレス環境へデプロイすることができます(awsgiはPythonの例ですが、GoやJavaScriptなどでも同様の戦略を取ることができます)

Lambdaはあくまでデプロイする先の環境の一つであるという捉え方をして開発すると、別のサーバーレス環境やPaaSへ移行したり、サーバーレスをやめることも簡単です。

サーバーレスの挙動を探る

いったん監視ツールの開発から離れて、サーバーレス自体がどのように実現されているのかを探ってみます。

handler.pyを書き換えて、デプロイ・実行してみてください。

import os
import platform

def hello(event, context):
    return f'''
    プラットフォーム    : {platform.platform()}
    実行中のディレクトリ: {os.getcwd()}
    実行中のユーザー    : {os.popen('whoami').read().strip()}
    起動時間            : {os.popen('uptime').read().strip()}
    CPU                 : {[l for l in open('/proc/cpuinfo') if l.startswith('model name')][0]}
    CPUコア数           : {os.cpu_count()}
    プロセス            : {os.popen('ps auxf').read()}
    '''

実行すると、次のような結果が出力されます。

    プラットフォーム    : Linux-4.9.93-41.60.amzn1.x86_64-x86_64-with-glibc2.2.5
    実行中のディレクトリ: /var/task
    実行中のユーザー    : sbx_user1065
    起動時間            : 21:00:51 up  5:26,  0 users,  load average: 0.00, 0.00, 0.00
    CPU                 : model name    : Intel(R) Xeon(R) CPU E5-2666 v3 @ 2.90GHz
    CPUコア数           : 2
    プロセス            : USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
                        482          1  0.6  0.4 170480 19244 ?        Ss   21:00   0:00 /var/lang/bin/python3.6 /var/runtime/awslambda/bootstrap.py
                        482          9  0.0  0.0 117184  2460 ?        R    21:00   0:00 ps auxf

いろいろな情報が分かって興味深いのですが、ここから次のことが予想できます。

  • Amazon Linux上で、Pythonのプロセスを実行している
  • bootstrap.pyがLambda関数をimportする形で実行されている
  • サンドボックスユーザーが作られ、/var/task内で実行される

Lambdaの処理の実態は、サンドボックス化(コンテナ化)されたAmazon Linux上で、ただプロセスを実行しているだけです。つまり、Dockerなどで似たような環境を作れば、MacやWindowsでも、またはCI環境であっても、本番のLambdaと同じような環境を再現することができます。

さらに、もう一度Lambda関数を実行してみてください。bootstrap.pyのプロセスのIDやSTARTが変化していないことが分かります。

    プロセス            : USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
                        482          1  0.0  0.4 170480 19264 ?        Ss   21:00   0:00 /var/lang/bin/python3.6 /var/runtime/awslambda/bootstrap.py
                        482         66  0.0  0.0 117184  2516 ?        R    21:14   0:00 ps auxf

このことから、プロセスを使い回しているということも分かります。サーバーレスを「リクエストに応じて、必要なリソースを確保する」と定義はしたのですが、これはあくまで思想的な話で、実際には効率化のためにリソースを使い回している可能性があるということです。

次のような場合には、プロセス/コンテナは破棄され、新しく作られます。気になる方は試してみてください。

  • コードをデプロイし直す
  • 一定時間以上、Lambda関数を実行しない
  • 同時に複数、Lambda関数を実行する

もっと学ぶには

この章では、監視ツール以外のサーバーレスで作れるシステムの紹介や、サーバーレスなアプリケーション開発現場での工夫、運用課題を見ていきたいと思います。

サーバーレスに作れるものの事例

今回作ったシステムは監視ツールでしたが、他にもサーバーレスに作れるものがあります。

本稿の途中でも何度か触れているように、サーバーレスなAPIを作る事例は多いようです。AWSやGoogle(Firebase)では、公式のチュートリアルが用意されています。

13 AWS上でサーバーレスウェブアプリケーションを構築する方法
14 はじめに: 最初の関数の記述とデプロイ | Firebase

次の紹介記事にあるように、サーバーレスなログ分析基盤を作ることもできます。NewsDigestでも類似したものを運用していますが(しかも一人で!)、ほぼ運用に時間は割いていませんし、大きなトラブルもありません。

15 Amazon Kinesis Firehose, Amazon Athena, Amazon QuickSightを用いたVPCフローログの分析 | Amazon Web Services ブログ

その他の事例では、AWSのリファレンスアーキテクチャも参考になります。

また、近年はAWS AppSync(サーバーレスなGraphQL API)や、AWS Glue(サーバーレスなSpark)、Cloud ML Engine(サーバーレスな機械学習)など、新たなサーバーレス関連製品が増えています。今はまだサーバーレスでないものも、サーバーレスに移行していく事例がどんどん増えていくと思います。

サーバーレスアプリケーションの継続的な開発

サーバーレスなアプリケーションの開発の本質は、サーバーレスでないアプリケーションの開発と変わりません。サーバーレスアプリケーションをチームで開発し、本番運用する際には、CI/CDであったり、コード品質の担保、デプロイしたアプリケーションの監視などが重要になってきます。

本記事ではデプロイにServerless Frameworkを使いましたが、Apexなどのいくつかのオープンソースなツールが存在します。ApexにはTerraformとの連携ができるメリットがあるため、JX通信社ではApexによるデプロイをCIに組み込むことが多いです。

コード品質を担保しようとすると、CIでの自動テストをどのように行うのかという問題も出てきます。テスタビリティを高めるという観点でも、特定のクラウドサービス依存しないようにプログラムを設計することが重要です。

また、マネージドサービスへのアクセスを含むシステムをテストする際には、localstackのような「マネージドサービスのモックをしたもの」を使うと、クラウドの利用費がかからず、テストに使ったリソースの破棄も容易なので便利です。

サーバーレスでの課題~システムの監視と管理

近年では、サーバーレスに関連して「observability」という言葉をよく目にします。マイクロサービス関連でも使われる言葉ですが、「複数の小さなシステムを組み合わせたときの監視をどうしていくか?」という点が運用の課題となっています。

AWSでは、公式の監視ツールとして、Amazon CloudWatchと、AWS X-rayがあります。Amazon CloudWatchは、AWSの各サービスのログやメトリクスを表示してくれるものです。AWS X-rayは複数システムの連携という観点から、プロファイリングが行えるようになっています。

16 Amazon CloudWatch(AWS リソースのモニタリング)| AWS
17 AWS X-Ray (製品や分散アプリケーションの分析とデバッグ) | AWS

AWS公式のものだけではなく、サードパーティのサービスもあります。いくつかのツールがServerless Frameworkのブログでも紹介されているので、監視に困ったら利用を検討してみてください。

18 Best tools for serverless observability - Serverless Blog

あわせてシステムの管理にも気を配りましょう。図「サーバーレスなアラートの投稿」サーバーレスなアラートへの発展に示したようなサーバーレスなシステムの設計をもっと大規模にすると、マネージドサービスが複雑に絡み合って管理が大変になっていく傾向にあります。

管理の観点からも、Serverless Frameworkのようなツールの利用を前提とすると良いでしょう。

まとめ

サーバーレスは近年盛り上がっている技術ではあるものの、サーバーレスに至る流れはかなり昔から続いており、その本質も旧来のアプリケーション開発と変わりません。

記事内で紹介したawsgiのように、既存のアプリケーションを簡単にサーバーレスに移行できるツールもあり、「PaaSのような特殊な実行環境のひとつ」にすぎないものだと考えています。しかし、うまくサーバーレス環境が活用できれば、インフラ運用コストを大幅に下げることができます。

本記事が、皆さまがサーバーレスでのシステム開発を始めてみる一助となれば幸いです。

執筆者プロフィール

小笠原みつき(おがさはら・みつき) 19 @yamitzky 20 @yamitzky

21
JX通信社のエンジニア執行役員として、ニュースの配信アルゴリズムの開発などを担当。サーバーレス関連の登壇はbuilderscon tokyo 2017、デブサミ2018など。

編集:薄井千春(ZINE)

若手ハイキャリアのスカウト転職