serverless frameworkを使って本格的なAPIサーバーを構築(魅力編)

serverless frameworkを使って本格的なAPIサーバーを構築(魅力編)

目次

  • serverless frameworkを使って本格的なAPIサーバーを構築(魅力編)← 今ここ
  • serverless frameworkを使って本格的なAPIサーバーを構築(ハンズオン編)
  • serverless frameworkを使って本格的なAPIサーバーを構築(Express編)
  • serverless frameworkを使って本格的なAPIサーバーを構築(MVC編)

framework_repo.png

こんにちは!某会社のインフラエンジニアをしておりますが、最近は サーバーレス という言葉が流行ってますね。
今回は、serverless frameworkで 「 lambda + APIGateway + DynamoDB 」 の構成でいくつかサービスを作ったので、利点などをまとめていけたらと思います。

サーバーレスとserverless framework

まずサーバーレスとは

サーバーの管理が不要になる

  • サーバーレスアーキテクチャでは、管理するサーバーがないため、サーバー管理から開放され、その分管理コストがかからなくなります。物理的なサーバーの管理はクラウド事業者に一任できるので、故障やパッチ適用の心配をする必要はありません。

また、一般にコード実行サービスは高い可用性を持っているため、オンプレミスサーバーよりも障害耐性が高くなるでしょう。

アプリ開発において便利

  • APIサーバーとして、ハッカソン用に作ったアプリの運用など、ほとんどアクセスがないようなAPIサーバーをずっと放置しておくのは大変お金がかかります。

従来の冗長化構成に比べ安価

  • EC2 + RDS環境を構築したときに、ある程度の規模であれば安価に済ますことができます。

サーバーの脆弱性を突いた攻撃が効かない

  • セキュリティ被害の多くはサーバーOSやミドルウェアの脆弱性を突いた攻撃ですが、公開ネットワーク上にサーバーが無いので、一般的なサーバーの脆弱性を突く攻撃が効きません。

  • Amazonのプラットフォームに脆弱性が無いとは言えませんが、プラットフォーム仕様が公開されていないので脆弱性を探すことが難しく、一般的なサーバーOSより脆弱性被害の可能性は少ないと考えられます。

サーバーが落ちることがない

  • サーバーが無いのでサーバーダウンの概念がありません。

  • 厳密にはAmazonがダウンすれば共倒れしますが、どのプラットフォームもサービス本体がダウンすれば同じことが言えるので、世界的インフラのAWSと、自社サーバーや一般企業のレンタルサーバーのどちらを信用するか、という話になります。

放置できる

  • セキュリティパッチや、サーバー自体の管理が発生しないためある程度放置できます。

インフラエンジニアがいなくてもスケールする

  • lambdaは最大で3000も同時に実行できます。
  • dynamodbは、キャパシティユニットとオートスケールの設定がかんたんにできます。
  • S3で静的コンテンツを配信したとしても障害確率はかなり低いです。

とにかく安い!

  • サーバーは初期導入コストが高く、必要になるサーバースペックも事前に見積もっておかなければいけません。物理障害時のパーツ交換費用なども必要です。
    クラウドサーバーにしても、起動している時間は料金がかかるため、閑散時などは100%コストを活用できているとはいえません。

  • 一方、コード実行サービスであれば、実際にコードを実行した時間に応じて課金されるため、ムダなコストが発生しません。初期導入コストもかからないため、システムの規模に合わせて柔軟にスケールできます。

保守メンテが楽

  • セキュリテイパッチやサーバー管理などのメンテが不要になります。

APIGateway+DynamoDB+Lambda手動構築の苦悩

image.png

  • serverless framework を使わずに「lambda + APIGateway + DynamoDB」を構築しようとすると、ほぼボタン操作で設定しないといけないです。
  • どのステージをデプロイするのか、設定した項目・手順を忘れた。もとに戻したい、引き継ぎを行いたいなどいろいろなケースが考えられます

再現性がない

  • これに尽きると思います。
  • 基本的にAPIGatewayの設定などはボタン操作です。もちろんgit管理などできない。
    ステージの作成、レスポンスコード、methodの作成など操作を間違えても元に戻すことが難しくなり、実装コスト、人件費、考えるだけで大変なことが多すぎます。(大変だった。)

よって保守性・メンテ・引き継ぎが難しくなっていました。

そこでserverless frameworkの出番です!!

image.png

  • 基本的にAWSコンソールでのボタン操作は不要!!
  • serverless.ymlですべて管理・設定ができる => 設定をgit管理できる

    • aws cloudformationの形式と似ているため、最悪それを真似できます(cloudformationよりかんたん!)
  • コマンド1発でデプロイとロールバックが可能
  • 公式リポジトリにサンプルが沢山!!(こまりません)
  • プラグインもたくさん(ぶっちゃけ使わなくても高機能!!)
  • Expressと組み合わせると、超便利!!!!!(後日記事を書きます)

注意点など

  • iamのadmin権限が必要です。
  • Cloudfrontは設定できません。

次回

  • serverless frameworkを使って本格的なAPIサーバーを構築(ハンズオン編)(仮)

参考記事

続きを読む

AWS Lambdaをlocal環境で実行できるaws-sam-localを試してみる

インストール

npmでインストールすることができます

$ npm install -g aws-sam-local

プロジェクト作成

適当なディレクトリを作成しSAMの公式プロジェクトのサンプルを参考に
index.jsとtemplate.yml を作成していきます

template.yml
AWSTemplateFormatVersion : '2017-09-20'

Description: A hello world application.

Resources:
  HelloWorld:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs6.10
index.js
'use strict';
console.log('Loading function');

exports.handler = (event, context, callback) => {
    callback(null, 'Hello World!');
};

実行

index.jsとtemplate.ymlが作成できたら実行してみます

※事前にDockerを起動しておく必要があります

$ echo '{}' | sam local invoke HelloWorld
2017/09/20 18:39:12 Successfully parsed template.yaml
2017/09/20 18:39:12 Connected to Docker 1.30
2017/09/20 18:39:12 Fetching lambci/lambda:nodejs6.10 image for nodejs6.10 runtime...
nodejs6.10: Pulling from lambci/lambda
Digest: sha256:b9a57b98dcfe226cac3fb0b8329594eefb62ba7089fa27cf8ac968e5736cc04a
Status: Image is up to date for lambci/lambda:nodejs6.10
2017/09/20 18:39:15 Reading invoke payload from stdin (you can also pass it from file with --event)
2017/09/20 18:39:15 Invoking index.handler (nodejs6.10)
START RequestId: feb31e2a-3748-1152-e1c1-85725b4021a7 Version: $LATEST
2017-09-20T09:39:16.722Z    feb31e2a-3748-1152-e1c1-85725b4021a7    Loading function
END RequestId: feb31e2a-3748-1152-e1c1-85725b4021a7
REPORT RequestId: feb31e2a-3748-1152-e1c1-85725b4021a7  Duration: 9.66 ms   Billed Duration: 0 ms   Memory Size: 0 MB   Max Memory Used: 29 MB

"Hello World!"


1回目の実行のときは Docker イメージの DLを行ってから実行されます

続きを読む

DynamoDB LocalとCognitoを併用する場合の注意点

先に結論

DynamoDB LocalとCognitoを併用する場合は、必ず別々のendpointを定義する。
特にCognito側のendpointを書く事例はほとんど見かけないので忘れがち。

utils/auth.js
import AWS from 'aws-sdk'

const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({
  apiVersion: '2016-04-18',
  region: 'ap-northeast-1',
  endpoint: `https://cognito-idp.ap-northeast-1.amazonaws.com/`,
})

export default function auth(AccessToken) {
  return new Promise((resolve, reject)=>{
    cognitoIdentityServiceProvider.getUser({AccessToken}, (err, data)=>{
      if (err) return reject(err)
      return resolve(data)
    })
  })
}
utils/dynamoose.js
import dynamoose from 'dynamoose'

dynamoose.AWS.config.update({region:'ap-northeast-1'})
if ( process.env.NODE_ENV === 'dev' ) {
  dynamoose.AWS.config.endpoint = new dynamoose.AWS.Endpoint('http://localhost:8000')
}
export default dynamoose

前提

  • 今回のケースではNode.jsのdynamooseというORMを使っているが、その他のケースでも発生する可能性があるので念のためメモ。
  • ServerlessFrameworkを使用しているが、それは今回の件とは関係ないはず?

経緯

  • ServerlessFrameworkのプラグイン serverless-dynamodb-local を使って、DynamoDB Localを起動し、Cognitoでユーザー管理をしようとした
  • アプリケーションを実行すると、1度目は成功するが2度目で必ず下記エラーが出ることに気づいた
MissingAuthenticationToken: Request must contain either a valid (registered) AWS access key ID or X.509 certificate.
  • AccessTokenが間違えてるのかなと思ったが合ってた
  • リージョン指定が間違えてるのかなと思ったが合ってた
  • DynamoDBの向き先をローカルではなく本番に向けたらエラーが出なかった
  • 向き先をローカルに戻して、CognitoのSDK側にendpointを指定したらエラーが出なかった

推察

  • 恐らくCognito User Poolは内部でDynamoDBを使っていて、DynamoDBのendpointを変えるとCognitoのSDK側にも影響を及ぼしてしまうのではないか?
  • このあたり詳しい人、もしくは中の人、今回の件について何かあれば教えてもらえたら嬉しいです

続きを読む

ECS運用のノウハウ

概要

ECSで本番運用を始めて早半年。ノウハウが溜まってきたので公開していきます。

設計

基本方針

基盤を設計する上で次のキーワードを意識した。

Immutable infrastructure

  • 一度構築したサーバは設定の変更を行わない
  • デプロイの度に新しいインフラを構築し、既存のインフラは都度破棄する

Infrastructure as Code (IaC)

  • インフラの構成をコードで管理
  • オーケストレーションツールの利用

Serverless architecture

  • 非常中型プロセスはイベントごとにコンテナを作成
  • 冗長化の設計が不要

アプリケーションレイヤに関して言えば、Twelve Factor Appも参考になる。コンテナ技術とも親和性が高い。

ECSとBeanstalk Multi-container Dockerの違い

以前に記事を書いたので、詳しくは下記参照。

Beanstalk Multi-container Dockerは、ECSを抽象化してRDSやログ管理の機能を合わせて提供してくれる。ボタンを何度か押すだけでRubyやNode.jsのアプリケーションが起動してしまう。
一見楽に見えるが、ブラックボックスな部分もありトラブルシュートでハマりやすいので、素直にECSを使った方が良いと思う。

ALBを使う

ECSでロードバランサを利用する場合、CLB(Classic Load Balancer)かALB(Application Load Balancer)を選択できるが、特別な理由がない限りALBを利用するべきである。
ALBはURLベースのルーティングやHTTP/2のサポート、パフォーマンスの向上など様々なメリットが挙げられるが、ECSを使う上での最大のメリットは動的ポートマッピングがサポートされたことである。
動的ポートマッピングを使うことで、1ホストに対し複数のタスク(例えば複数のNginx)を稼働させることが可能となり、ECSクラスタのリソースを有効活用することが可能となる。

※1: ALBの監視方式はHTTP/HTTPSのため、TCPポートが必要となるミドルウェアは現状ALBを利用できない。

アプリケーションの設定は環境変数で管理

Twelve Factor Appでも述べられてるが、アプリケーションの設定は環境変数で管理している。
ECSのタスク定義パラメータとして環境変数を定義し、パスワードやシークレットキーなど、秘匿化が必要な値に関してはKMSで暗号化。CIによってECSにデプロイが走るタイミングで復号化を行っている。

ログドライバ

ECSにおいてコンテナはデプロイの度に破棄・生成されるため、アプリケーションを始めとする各種ログはコンテナの内部に置くことはできない。ログはイベントストリームとして扱い、コンテナとは別のストレージで保管する必要がある。

今回はログの永続化・可視化を考慮した上で、AWSが提供するElasticsearch Service(Kibana)を採用することにした。
ECSは標準でCloudWatch Logsをサポートしているため、当初は素直にawslogsドライバを利用していた。CloudWatchに転送してしまえば、Elasticsearch Serviceへのストリーミングも容易だったからである。

Network (3).png

しかし、Railsで開発したアプリケーションは例外をスタックトレースで出力し、改行単位でストリームに流されるためログの閲覧やエラー検知が非常に不便なものだった。
Multiline codec plugin等のプラグインを使えば複数行で構成されるメッセージを1行に集約できるが、AWS(Elasticsearch Service)ではプラグインのインストールがサポートされていない。
EC2にElasticsearchを構築することも一瞬考えたが、Elasticsearchへの依存度が高く、将来的にログドライバを変更する際の弊害になると考えて止めた。
その後考案したのがFluentd経由でログをElasticsearch Serviceに流す方法。この手法であればFluentdでメッセージの集約や通知もできるし、将来的にログドライバを変更することも比較的容易となる。

Network (4).png

ジョブスケジューリング

アプリケーションをコンテナで運用する際、スケジュールで定期実行したい処理はどのように実現するべきか。
いくつか方法はあるが、1つの手段としてLambdaのスケジュールイベントからタスクを叩く方法がある(Run task)。この方法でも問題はないが、最近(2017年6月)になってECSにScheduled Taskという機能が追加されており、Lambdaに置き換えて利用可能となった。Cron形式もサポートしているので非常に使いやすい。

運用

ECSで設定可能なパラメータ

ECSコンテナインスタンスにはコンテナエージェントが常駐しており、パラメータを変更することでECSの動作を調整できる。設定ファイルの場所は /etc/ecs/ecs.config
変更する可能性が高いパラメータは下表の通り。他にも様々なパラメータが存在する。

パラメータ名 説明 デフォルト値
ECS_LOGLEVEL ECSが出力するログのレベル info
ECS_AVAILABLE_LOGGING_DRIVERS 有効なログドライバの一覧 [“json-file”,”awslogs”]
ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION タスクが停止してからコンテナが削除されるまでの待機時間 3h
ECS_IMAGE_CLEANUP_INTERVAL イメージ自動クリーンアップの間隔 30m
ECS_IMAGE_MINIMUM_CLEANUP_AGE イメージ取得から自動クリーンアップが始まるまでの間隔 1h

パラメータ変更後はエージェントの再起動が必要。

$ sudo stop ecs
$ sudo start ecs

クラスタのスケールアウトを考慮し、ecs.configはUserDataに定義しておくと良い。
以下はfluentdを有効にしたUserDataの記述例。

#!/bin/bash
echo ECS_CLUSTER=sandbox >> /etc/ecs/ecs.config
echo ECS_AVAILABLE_LOGGING_DRIVERS=["fluentd"] >> /etc/ecs/ecs.config

CPUリソースの制限

現状ECSにおいてCPUリソースの制限を設定することはできない(docker runの--cpu-quotaオプションがサポートされていない)。
タスク定義パラメータcpuは、docker runの--cpu-sharesにマッピングされるもので、CPUの優先度を決定するオプションである。従って、あるコンテナがCPUを食いつぶしてしまうと、他のコンテナにも影響が出てしまう。
尚、Docker 1.13からは直感的にCPUリソースを制限ができる--cpusオプションが追加されている。是非ECSにも取り入れて欲しい。

ユーティリティ

実際に利用しているツールを紹介。

graph.png

ルートボリューム・Dockerボリュームのディスク拡張

ECSコンテナインスタンスは自動で2つのボリュームを作成する。1つはOS領域(/dev/xvda 8GB)、もう1つがDocker領域(/dev/xvdcz 22GB)である。
クラスタ作成時にDocker領域のサイズを変更することはできるが、OS領域は項目が見当たらず変更が出来ないように見える。

Screen_Shot_2017-08-31_at_11_00_03.png

どこから設定するかというと、一度空のクラスタを作成し、EC2マネージメントコンソールからインスタンスを作成する必要がある。

また、既存ECSコンテナインスタンスのOS領域を拡張したい場合は、EC2マネージメントコンソールのEBS項目から変更可能。スケールアウトを考慮し、Auto scallingのLaunch Configurationも忘れずに更新しておく必要がある。

補足となるが、Docker領域はOS上にマウントされていないため、ECSコンテナインスタンス上からdf等のコマンドで領域を確認することはできない。

デプロイ

ECSのデプロイツールは色々ある(ecs_deployerは自分が公開している)。

社内で運用する際はECSでCIを回せるよう、ecs_deployerをコアライブラリとしたCIサーバを構築した。

デプロイ方式

  • コマンド実行形式のデプロイ
  • GitHubのPushを検知した自動デプロイ
  • Slackを利用したインタラクティブデプロイ

Screen_Shot_2017-08-31_at_11_31_15.png

デプロイフロー

ECSへのデプロイフローは次の通り。

  1. リポジトリ・タスクの取得
  2. イメージのビルド
    • タグにGitHubのコミットID、デプロイ日時を追加
  3. ECRへのプッシュ
  4. タスクの更新
  5. 不要なイメージの削除
    • ECRは1リポジトリ辺り最大1,000のイメージを保管できる
  6. サービスの更新
  7. タスクの入れ替えを監視
    • コンテナの異常終了も検知
  8. Slackにデプロイ完了通知を送信

現在のところローリングデプロイを採用しているが、デプロイの実行から完了までにおよそ5〜10分程度の時間を要している。デプロイのパフォーマンスに関してはまだあまり調査していない。

ログの分類

ECSのログを分類してみた。

ログの種別 ログの場所 備考
サービス AWS ECSコンソール サービス一覧ページのEventタブ APIで取得可能
タスク AWS ECSコンソール クラスタページのTasksタブから”Desired task status”が”Stopped”のタスクを選択。タスク名のリンクから停止した理由を確認できる APIで取得可能
Docker daemon ECSコンテナインスタンス /var/log/docker ※1
ecs-init upstart ジョブ ECSコンテナインスタンス /var/log/ecs/ecs-init.log ※1
ECSコンテナエージェント ECSコンテナインスタンス /var/log/ecs/ecs-agent.log ※1
IAMロール ECSコンテナインスタンス /var/log/ecs/audit.log タスクに認証情報のIAM使用時のみ
アプリケーション コンテナ /var/lib/docker/containers ログドライバで変更可能

※1: ECSコンテナインスタンス上の各種ログは、CloudWatch Logs Agentを使うことでCloudWatch Logsに転送することが可能(現状の運用ではログをFluentdサーバに集約させているので、ECSコンテナインスタンスにはFluentdクライアントを構築している)。

サーバレス化

ECSから少し話が逸れるが、インフラの運用・保守コストを下げるため、Lambda(Node.js)による監視の自動化を進めている。各種バックアップからシステムの異常検知・通知までをすべてコード化することで、サービスのスケールアウトに耐えうる構成が容易に構築できるようになる。
ECS+Lambdaを使ったコンテナ運用に切り替えてから、EC2の構築が必要となるのは踏み台くらいだった。

トラブルシュート

ログドライバにfluentdを使うとログの欠損が起きる

ログドライバの項に書いた通り、アプリケーションログはFluentd経由でElasticsearchに流していたが、一部のログが転送されないことに気付いた。
構成的にはアプリケーションクラスタからログクラスタ(CLB)を経由してログを流していたが、どうもCLBのアイドルタイムアウト経過後の最初のログ数件でロストが生じている。試しにCLBを外してみるとロストは起きない。

Network (1).png

ログクラスタ(ECSコンテナインスタンスの/var/log/docker)には次のようなログが残っていた。

time="2017-08-24T11:23:55.152541218Z" level=error msg="Failed to log msg "..." for logger fluentd: write tcp *.*.*.*:36756->*.*.*.*:24224: write: broken pipe"
3) time="2017-08-24T11:23:57.172518425Z" level=error msg="Failed to log msg "..." for logger fluentd: fluent#send: can't send logs, client is reconnecting"

同様の問題をIssueで見つけたが、どうも現状のECSログドライバはKeepAliveの仕組みが無いため、アイドルタイムアウトの期間中にログの送信が無いとELBが切断してしまうらしい(AWSサポートにも問い合わせた)。

という訳でログクラスタにはCLBを使わず、Route53のWeighted Routingでリクエストを分散することにした。

Network (2).png

尚、この方式ではログクラスタのスケールイン・アウトに合わせてRoute 53のレコードを更新する必要がある。
ここではオートスケールの更新をSNS経由でLambdaに検知させ、適宜レコードを更新する仕組みを取った。

コンテナの起動が失敗し続け、ディスクフルが発生する

ECSはタスクの起動が失敗すると数十秒間隔でリトライを実施する。この時コンテナがDockerボリュームを使用していると、ECSコンテナエージェントによるクリーンアップが間に合わず、ディスクフルが発生することがあった(ECSコンテナインスタンスの/var/lib/docker/volumesにボリュームが残り続けてしまう)。
この問題を回避するには、ECSコンテナインスタンスのOS領域(※1)を拡張するか、コンテナクリーンアップの間隔を調整する必要がある。
コンテナを削除する間隔はECS_ENGINE_TASK_CLEANUP_WAIT_DURATIONパラメータを使うと良い。

※1: DockerボリュームはDocker領域ではなく、OS領域に保存される。OS領域の容量はデフォルトで8GBなので注意が必要。

また、どういう訳か稀に古いボリュームが削除されず残り続けてしまうことがあった。そんな時は次のコマンドでボリュームを削除しておく。

# コンテナから参照されていないボリュームの確認
docker volume ls -f dangling=true

# 未参照ボリュームの削除
docker volume rm $(docker volume ls -q -f dangling=true)

ECSがELBに紐付くタイミング

DockerfileのCMDでスクリプトを実行するケースは多々あると思うが、コンテナはCMDが実行された直後にELBに紐付いてしまうので注意が必要となる。

bundle exec rake assets:precompile

このようなコマンドをスクリプトで実行する場合、アセットがコンパイル中であろうがお構いなしにELBに紐付いてしまう。
時間のかかる処理は素直にDockerfile内で実行した方が良い。

続きを読む

AWS Lambda(C#)からAuroraにつないでみる

Serverless Meetup Tokyo #4に参加してきたら

  • AWS SAから「ServerlessにはAuroraは不向き」的な話をされた
  • ENI作成時間が気になるなら、定期的にLambdaの暖機が必要

のような逆風にも負けずLambdaからAuroraに接続するアツい話を聞いて、無性にLambda(C#)からAuroraに接続したくなったので試してみました。

そう言えば「LambdaならC#よりnode.js/Pythonの方がオススメ」的な話も聞いたことがあるような…

開発環境

  • Mac
  • .Net Core 2.0.0 / .Net Core 1.0.5共存環境

本来Lambdaの作成には.Net Core 2.0.0は不要ですが
 ・自分の環境は1.0と2.0の共存環境で
 ・2.0.0になってdotnet restoreの振る舞いが変わった
ため、dotnetコマンドのバージョンが2.0.0の場合で記載します。

作業概要

  1. .Net Coreのインストール
  2. Auroraの作成
  3. C#コード作成
  4. デプロイ用パッケージの作成
  5. Lambdaの実行

1. .Net Coreのインストール

MicrosoftのサイトからMac用をダウンロードしてインストールします。

2.0.0
1.0.5 with SDK 1.0.4

2. Auroraの作成

AWSマネジメントコンソールからAuroraを構築します。

※留意点

  • VPC内からアクセスするため、Publicly Accessible:Noにします
  • お試しなら、インスタンスタイプは一番料金が安いdb.t2.smallがオススメです

3. C#コード作成

適当な作業ディレクトリを作成し、その配下に.csファイルを作成します。
またAuroraに接続するために、下記値を設定します。

  • クラスタエンドポイント
  • ユーザ / パスワード
  • データベース名
MyFunction.cs
using Amazon.Lambda.Core;
using MySql.Data.MySqlClient;

namespace sample_aurora
{
    public class MyFunction
    {
        const string ConnectionString = "<<クラスタエンドポイント>>;user=<<ユーザ>>;password=<<パスワード>>;port=3306;database=<<データベース名>>;SslMode=None";

        [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
        public string MyHandler(LambdaEvent lambdaEvent, ILambdaContext context)
        {
            string command = lambdaEvent.Command;
            LambdaLogger.Log("Command: " + command + "n");
            switch(command){
                case "select":
                    Select();
                    break;
                case "insert":
                    Insert();
                    break;
                case "init":
                    Init();
                    break;
                default:
                    LambdaLogger.Log("Do nothing.n");
                    break;

            }
            return "Sample function was executed.";
        }

        private void Select()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("select id from test_tbl;", connection);

            string message = "Data: ";

            using (MySqlDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    string row = $"{reader["id"]}";
                    message = message + "," + row;
                }
            }

            connection.Close();

            LambdaLogger.Log(message + "n");
        }

        private void Insert()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("insert into test_tbl values ();", connection);

            command.ExecuteNonQuery();

            connection.Close();

            LambdaLogger.Log("A record has been inserted.n");
        }

        private void Init()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("create table if not exists test_tbl (id int auto_increment primary key);", connection);

            command.ExecuteNonQuery();

            connection.Close();

            LambdaLogger.Log("A table has been created.n");
        }
    }

    public class LambdaEvent
    {
        public string Command { get; set; }
    }
}

4. デプロイ用パッケージの作成

4-1. .csprojファイルの作成

同じ作業ディレクトリに.csprojファイルを作成します。

sample_aurora.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.TestUtilities" Version="1.0.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
    <PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
    <PackageReference Include="MySql.Data.Core" Version="7.0.4-IR-191" />
  </ItemGroup>
</Project>

4-2. デプロイパッケージの作成

作業ディレクトリ内で以下コマンドを実行します。

$ dotnet restore
$ dotnet publish --output publish
$ cp ~/.nuget/packages/system.data.sqlclient/4.1.0/runtimes/unix/lib/netstandard1.3/System.Data.SqlClient.dll publish/
$ cp ~/.nuget/packages/system.diagnostics.tracesource/4.0.0/runtimes/unix/lib/netstandard1.3/System.Diagnostics.TraceSource.dll pulish/
$ cd publish
$ zip ../sample_aurora.zip *

※留意点

  • System.Data.SqlClient.dllとSystem.Diagnostics.TraceSource.dllはdotnet publishでは含まれなかったため、手動でコピーしています。(無いとLambda実行時にエラーになります)

4-3. Lamdbaのデプロイ

AWSマネジメントコンソールからLambdaを選んで、「sample_aurora.zip」をデプロイします。

  • VPC対応にします
  • Auroraに接続できるsecurity groupを指定します
  • もしくは、指定したsubnet groupがaurora側のsecurity groupで許可されていれば、lamdbaで指定するsecurity grouopはダミーで構いません

5. Lambdaの実行

AWSマネジメントコンソールからLambdaを実行します。

5-1. テーブル作成

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "init"
}

Log outputに以下出力されます。

Command: init
A table has been created.

5-2. Insert

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "insert"
}

Log outputに以下出力されます。

Command: insert
A record has been inserted.

実行した回数分テーブルにレコードが追加されます。

5-3. Select

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "select"
}

Log outputに以下出力されます。

Command: select
Data: ,1,2,3

雑感

案外簡単にC# LambdaからAuroraへ接続できました。

続きを読む

sam-localは初見では素晴らしいと思ったんだけどなぁ… – daikikohara のコメント / はてなブックマーク

AWS以外のサポートも手厚いのでやっぱりServerless Frameworkかなって思った。慣れればそこまでローカル開発辛くないし。sam-localは初見では素晴らしい … 続きを読む