定期的にデータを取得して保存したい案件があるのですが、データベース作りたくないし、管理したくないし、マネージドなものはお金が高いのでS3で代替してみようという試み。加えて定期実行する環境も管理したくないし、コードの更新も簡単にできるようにしたい。要するにリソースはCloudFormationで管理したい。これをタイトルの組み合わせで実現します。

環境


  • AWS Lambda (Node.js 12.x)
  • Serverless Framework 1.66.0

事前準備


アップロード先のS3バケットを作成しておきます。

Serverless Framework


今回は Serverless Frameworkを使います。Serverless FrameworkでデプロイするとCloudFormationで関連するStack(Lambdaなど)が出来上がるのでリソースの管理も楽になります。

インストール

プロジェクトごとにインストールしてもよいのですが、個人でやるやつだし、グローバルインストールした方がコマンド打つのが楽なのでグローバルインストールします。

1
$ npm install -g serverless

プロジェクト作る

AWS Node.js用のテンプレートを利用してプロジェクトを作成します。

1
$ serverless create --template aws-nodejs --path test-job

test-jobディレクトリ配下にサンプルのプロジェクトが出来上がってます。

serverless.yaml

次にserverless.yamlを編集していきます。設定については下のコードのコメント見てください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
service: test-jobs

provider:
name: aws
runtime: nodejs12.x
region: ap-northeast-1 # 指定しないとバージニアかオレゴンあたりにデプロイされる
stage: prd # 指定しない場合 dev になる。AWSのリソース名に付与される。複数環境扱うときに便利。
profile: sls # 前述の credentials に書いた profile
timeout: 10 # Lambdaの処理時間。処理内容に合わせて変更。デフォルトは6秒。

iamRoleStatements: # S3にファイル作成するのに必要な権限
- Effect: Allow
Action:
- 's3:PutObject'
- 's3:PutObjectAcl'
Resource:
- 'arn:aws:s3:::${self:custom.S3_BUCKET_NAME}/*'
environment:
S3_BUCKET_NAME: ${self:custom.S3_BUCKET_NAME}

custom:
S3_BUCKET_NAME: yourbucketname.example # デプロイ先のバケット

package:
individually: true
exclude:
- '**/**'
include:
- 'handlers/*'

functions:
createFile: # Lambdaの関数名になる
handler: handlers/file.create # Lambdaのコード。ファイルと関数名を指定する
events:
- schedule: cron(0/1 * * * ? *) # 定期実行をCronで指定

デプロイ用のユーザ作成と権限追加

Serverless Frameworkのデプロイに使うIAMユーザを作成してそのaws_access_key_idaws_secret_access_keyを控えます。控えて~./aws/credentialsにデプロイ用のprofileslsを作成します。

1
2
3
[sls]
aws_access_key_id=<YOUR_ACCESS_KEY_ID>
aws_secret_access_key=<YOUR_SECRET_ACCESS_KEY_ID>

後はこのユーザに権限付与します。下記のあたりのアクセス権限が必要です。

  • Lambda
  • IAM
  • S3
  • CloudWatch
  • CloudFormation

権限が不足していると後述するsls deployでデプロイする際にどの権限がないか怒られるのでトライ&エラーで一つずつ調べる(しかないのか?)必要があります。私は自分の個人アカウントだし調べるのが手間なので上記5つのサービスのFullAccessを付与しましたが、恐い人はちゃんと調べて細かく設定した方がいいと思います。

パッケージインストール


AWS SDK for JavaScriptを使うのでパッケージインストールします。

1
$ npm install aws-sdk

コード書く


次にS3にアップロードするJavaScriptのコードを書きます。serverless.yamlhandlers/file.createを指定しているのでhanderlsディレクトリにfile.jsを作成します。こんな感じのディレクトリ構成になる。

1
2
3
4
5
6
.
├── handlers
│ └── file.js
├── package.json
├── package-lock.json
└── serverless.yml

続けてfile.jsに下記のコードを書きます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'use strict';

const S3 = require('aws-sdk/clients/s3');
const S3_BUCKET = process.env.S3_BUCKET_NAME;

module.exports.create = async () => {

const upload = new S3.ManagedUpload({
params: {
Bucket: S3_BUCKET,
Key: 'test/text.txt',
Body: 'hogehogehogehoge',
ContentType: 'text/plain',
ACL: "public-read"
}
});

await upload.promise().then((data, err) => {
if (err) {
return console.log(err);
}
return console.log(data);
});
};

ローカルでデプロイしてみる


Serverless FrameworkにはデプロイしなくてもLambdaの動きをローカルでエミュレート(完全にLambda通りに動くわけではない)するコマンドがあるので、AWSにデプロイする前にそれで動作するかどうか試します。

1
2
3
4
5
6
7
8
$ sls invoke local --function createFile
{
ETag: '"2g54663h48890aggf403a26n9kl9180m"',
Location: 'https://s3.ap-northeast-1.amazonaws.com/yourbucketname.example/test/text.txt',
key: 'test/text.txt',
Key: 'test/text.txt',
Bucket: 'yourbucketname.example'
}

成功すると上記のような表示されてバケットtest/text.txtにファイルが作成されてhogehogehogehogeという文字が書き込まれているはずです。

デプロイする


下記のコマンドでデプロイします。serverless.yamlprovider.profileで指定したprofileでデプロイが始まります。

1
$ sls deploy

また、関数だけの変更(serverless.yamlを変更しなければ)であれば初回以降は下記のコマンドで関数ごとにデプロイできます。

1
$ sls deploy function -f createFile

成功すると下記のようにCloudFormationのスタックができて、Lambdaの関数も作成されます。

後はserverless.yamlで指定した通りに定期的にファイルが作成されるはずです。

実行結果はServerless FrameworkがCloudFormationで作成したCloudWatchに出力されます。前述のコードだとエラーの場合はエラーの内容が、成功した場合はETagなどの情報が記録されます。

おわり。

参考