2
Help us understand the problem. What are the problem?

posted at

updated at

【Serverless Framework】S3アップロードしたらLambda実行できるようにする。

この記事のゴール

・Serverless Frameworkの基本的な使い方を学ぶ。
・ESLint × PrettierでTypeScriptのソースコードを自動整形できるようにする。
・S3アップロードイベントで実行されるLambda関数を作成してみる。
  → 実用性がありそう&応用が効きそうなケース

image.png

もくじ

o1. Serverless Frameworkプロジェクト基盤作成
o2. S3アップロード時にLambdaが発火するようにする。
o3. CSVファイル読込
o4. mysqlインポート

ソースコード

o1. Serverless Frameworkプロジェクト基盤作成

プロジェクトフォルダ作成&npm initでpackage.json作成

mkdir serverless-framework
cd serverless-framework
npm init

serverless CLIインストール

npm install -g serverless

必要なライブラリ追加

// TypeScript
npm install --save-dev typescript

// Serverless関連
npm install --save-dev serverless-offline
npm install --save-dev serverless-offline-lambda
npm install --save-dev serverless-s3-local
npm install --save-dev serverless-plugin-typescript

// AWS関連
npm install aws-lambda
npm install --save-dev @types/aws-lambda
npm install aws-sdk
npm install --save-dev @types/aws-sdk

// CSVパース関連
npm install async
npm install --save-dev @types/aws-sdk
npm install csv-parse
npm install iconv-lite

// DB関連
npm install promise-mysql

TypeScriptコンパイル設定(tsconfig.json追加)

TypeScriptコンパイル用の設定ファイル(tsconfig.json)をルートディレクトリに追加する。tsconfig.jsonの詳細はこちら

{
    "compilerOptions": {
      "target": "es2020",
      "module": "commonjs",
      "lib": ["es2020"],
      "sourceMap": true,
      "outDir": "./dist",
      "rootDir": "./src",
      "strict": true,
      "moduleResolution": "node",
      "baseUrl": "src",
      "esModuleInterop": true,
      "experimentalDecorators": true,
      "emitDecoratorMetadata": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true
    },
    "include": ["src/**/*"],
    "exclude": ["dist", "node_modules"],
}

ESLint導入

・ESLintとはJavaScriptのリンター(ソースコードを分析し問題点を指摘してくれる静的解析ツール)である。
JavaScriptのリンターであるESLintにTypeScriptを適用するためには、下記のプラグインが必要なので追加する。

npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser

lintコマンドでエラー検知できるようにする

ルート直下に.eslintrc.jsonを作成。

{
    "root": true, // .eslintrc.jsonがプロジェクトのルートに配置させているか(指定がないと上位階層へ設定ファイルを探索される)
    "parser": "@typescript-eslint/parser", // ESLintにTypeScriptを理解させる
    "parserOptions": {
      "project": "./tsconfig.json" // tsconfig.jsonの場所を指定
    },
    "plugins": [
      "@typescript-eslint" // ESLintのTypeScriptプラグインのルールを適用できる様にする(/eslint-pluginは省略可)
    ],
    "extends": [
      "eslint:recommended", // ESLintのJavaScriptルールセットを適用
      "plugin:@typescript-eslint/eslint-recommended", // eslint:recommendedに含まれるルールを型チェックでカバーできるものは無効化
      "plugin:@typescript-eslint/recommended-requiring-type-checking", // 型チェックが必要なルールを適用
    ]
}

package.jsonにlintコマンドを追加する。

  "scripts": {
    "lint": "eslint . --ext ts"
  }

試しに下記のsrc/hello.tsを作成してみて、yarn lintを実行してみる。

var hello = undefined;

ちゃんと怒られる。

% yarn lint
yarn run v1.22.17
$ eslint . --ext ts

/Users/daisuke/Desktop/serverless-practice/src/hello.ts
  1:1  error  Unexpected var, use let or const instead    no-var
  1:5  error  'hello' is assigned a value but never used  no-unused-vars

✖ 2 problems (2 errors, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.

修正して再度yarn lint実行。

const hello = "";
console.log(hello);

無事にlintコマンドが通るようになった!

% yarn lint
yarn run v1.22.17
$ eslint . --ext ts
✨  Done in 3.29s.

ファイル保存時に自動フォーマットを適用する

Prettierインストール

コードを自動整形してくれるツールとしてPrettierをインストールする。

npm install --save-dev prettier eslint-config-prettier

 ESLint「ここのソースコード、汚えから整形した方がええで」
 Prettier「おk。整形しとくわ」

というように、ESLintで設定したルールに基づいて自動フォーマットを行うには「ESLint✖️Prettier」の両方必要というわけだ。

eslint-config-prettierは ESLint のコードフォーマットに関連するルールを無効化し、バグを検出するルールのみを有効にするプラグイン。

.eslintrc.jsonのextendsにPrettierを追加

{
  /* 中略 */
  extends: [
    /* 中略 */
    "prettier", // 追加。他の設定の上書きを行うために、必ず最後に配置する。
  ],
};

VSCode拡張機能インストール

ESLint(dbaeumer.vscode-eslint)と Prettier(esbenp.prettier-vscode) の拡張機能をインストールする。
image.png
image.png

.vscode/settings.json追加

ルート直下に.vscode/settings.jsonを追加する。

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true 
}

ここまでの設定を完了すれば、VSCodeでのファイル保存時に.eslintrc.jsonの設定に基づいてPrettierが自動フォーマットしてくれる👍

o2. S3アップロード時にLambdaが発火するようにする。

準備系が一通り終わったので、次にS3アップロードしたらLambdaを発火させるというのをローカル環境で動作確認できるようにする。

serverless.yml追加

Serverless Frameworkの設定ファイル。

service: serverless-practice

frameworkVersion: '3'

// プロバイダ(AWS/GCP/Azure)関連の設定
provider:
  name: aws
  runtime: nodejs14.x
  region: ${opt:region, 'us-east-1'}
  stage: ${opt:stage, 'offline'}
  endpointType: private
  // process.env.XXXX で参照したい変数をここに定義する。
  environment: ${file(serverless-config/env/env.${self:provider.stage}.yml)}

plugins:
  // ---.tsをコンパイル対象にする
  - serverless-plugin-typescript
  // ローカル環境でLambda動作確認用
  - serverless-offline-lambda
  - serverless-offline
  // ローカル環境でS3動作確認用
  - serverless-s3-local

custom:
  // serverless-s3-local用の情報
  // ./tmpで http://localhosst:4568 でS3をホストする
  s3:
    host: localhost
    port: 4569
    directory: ./tests/resources

// sls offline start, sls deploy時にS3バケットを作成する。
resources:
  Resources:
    NewResource:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: local-bucket

// Lambda関数
functions:
  csvRead:
    handler: src/functions/csvRead/handler.run 
    events:
      - s3:
          bucket: local-bucket
          event: s3:ObjectCreated:*

src/functions/csvRead/handler.ts追加

import { S3Event } from "aws-lambda";

export const run = (event: S3Event) => {
  if (!event.Records || event.Records.length == 0) {
    return null;
  }
  console.log(`BucketName: ${event.Records[0].s3.bucket.name}`);
  console.log(`ObjectName: ${event.Records[0].s3.object.key}`);
};

動作確認

ローカルS3接続用のAWS認証情報設定

% aws configure --profile s3local
AWS Access Key ID [None]: S3RVER
AWS Secret Access Key [None]: S3RVER 
Default region name [None]: us-east-1
Default output format [None]: json

テスト用CSVファイル用意 / ローカルS3バケット用のフォルダ作成

・ルート直下にtests/resources/sample_user.csvを作成する。
image.png

Lambda/S3バケットをローカル起動

% npx sls offline start
starting handler
Found S3 event listener for local-bucket
Offline Lambda Server listening on http://localhost:4000
S3 local started ( port:4569, family: IPv4, address: 127.0.0.1 )
Compiling with Typescript...
Using local tsconfig.json - tsconfig.json
Warning: "rootDir" from local tsconfig.json is overriden
Typescript compiled.
Watching typescript files...

Starting Offline at stage dev (us-east-1)

Offline [http for lambda] listening on http://localhost:3002
Function names exposed for local invocation by aws-sdk:
           * csvRead: serverless-practice-dev-csvRead

ローカルS3にファイルアップロード

別のターミナル/コマンドプロンプトを起動して、S3アップロードコマンドを実行する。

% aws --endpoint http://localhost:4569 --profile s3local s3 cp ./tests/resources/sample_user.csv s3://local-bucket/sample_user.csv
upload: tests/resources/sample_user.csv to s3://local-bucket/sample_user.csv

npx sls offline startを実行したターミナル側では、以下のログが出力される。

info: Stored object "sample_user.csv" in bucket "local-bucket" successfully
BucketName: local-bucket
ObjectName: sample_user.csv
info: PUT /local-bucket/sample_user.csv 200 46ms 0b

・S3バケット(local-bucket)にsample_user.csvが無事にアップロードされたこと
・アップロードをトリガーにLambda関数(src/functions/csvRead/handler.run)起動されていること
が確認できる。

o3. CSVファイル読込

S3にアップロードされたCSVファイルの中身を読み込んで標準出力してみる。

src/utils/s3Client.ts追加

import AWS from "aws-sdk";
import * as stream from "stream";

let S3: AWS.S3 | null = null;
async function connectS3(): Promise<AWS.S3> {
  if (S3 == null) {
    if (process.env.NODE_ENV == "offline") {
      S3 = new AWS.S3({
        // force urls like http://{host}/{bucket}/{key} instead of http://{bucket}.
        s3ForcePathStyle: true,
        endpoint: new AWS.Endpoint(process.env.S3_ENDPOINT || ""),
        accessKeyId: "S3RVER",
        secretAccessKey: "S3RVER",
      });
    } else {
      S3 = new AWS.S3({
        // force urls like http://{host}/{bucket}/{key} instead of http://{bucket}.
        s3ForcePathStyle: true,
        endpoint: new AWS.Endpoint(process.env.S3_ENDPOINT || ""),
      });
    }
  }
  return S3;
}

export async function getObjectReadStream(key: string): Promise<stream.Readable> {
  const request: AWS.S3.GetObjectRequest = {
    Bucket: process.env.S3_BUCKET || "",
    Key: key,
  };
  const s3: AWS.S3 = await connectS3();
  return await s3
    .getObject(request)
    .createReadStream()
    .on("error", (err: Error) => {
      console.log(err);
      return null;
    });
}

環境変数定義ファイル追加

S3やDB関連のように環境ごとに値が変わるものはserverless-config/env/env.${ENV}.ymlに定義する。

NODE_ENV: ${self:provider.stage}

// S3
S3_ENDPOINT: http://localhost:4569
S3_BUCKET: local-bucket

src/functions/csvRead/handler.ts修正

・CSVパーサでファイル読込&標準出力する。

import { S3Event } from "aws-lambda";
import { Parser, parse } from "csv-parse";
import iconv from "iconv-lite";
import { getObjectReadStream } from "../../utils/s3Client";

export const run = async (event: S3Event) => {
  if (!event.Records || event.Records.length == 0) {
    return null;
  }
  // get csv file's s3 object key
  const objectKey = event.Records[0].s3.object.key;

  // configure how csv parser processes each row
  const parser: Parser = parse();
  parser.on("readable", () => {
    let row;
    while ((row = parser.read())) {
      console.log(row);
    }
  });
  // read csv file
  const csvReadStream = await getObjectReadStream(objectKey);
  // write stream into csv parser
  csvReadStream.pipe(iconv.decodeStream("Shift_JIS")).pipe(parser);
};

動作確認

・CSVファイル(tests/resources/sample_user.csv)に下記を追記する。

'00001','Anthony',10
'00002','Bryant',20
'00003','Carmelo',30

・先ほどと同じくnpx sls offline startでローカル起動してから、別ターミナルからCSVファイルをS3にアップロードする。

% aws --endpoint http://localhost:4569 --profile s3local s3 cp ./tests/resources/sample_user.csv s3://local-bucket/sample_user.csv
% npx sls offline start
  〜〜一部割愛〜〜
Function names exposed for local invocation by aws-sdk:
           * csvRead: serverless-practice-offline-csvRead
info: Stored object "sample_user.csv" in bucket "local-bucket" successfully
offline: (λ: csvRead) RequestId: cl36vrfq70000rgo03zy44q38  Duration: 533.83 ms  Billed Duration: 534 ms
info: PUT /local-bucket/sample_user.csv 200 577ms 0b
info: GET /local-bucket/sample_user.csv 200 8ms 84b
[ '00001', 'Anthony', 10 ]
[ '00002', 'Bryant', 20 ]
[ '00003', 'Carmelo',30 ]

↑の通り、S3にアップロードしたCSVファイルが読み込まれ、中身が標準出力に表示されている👍

o4. mysqlインポート

CSVファイルに格納されているデータがDB投入用データと仮定して、次の動きを実現する。
S3アップロード → CSVファイル読込 → MySQLにデータ投入

docker-compose.yml追加

・ローカルでMySQLコンテナを起動するためのdocker-compose.ymlをルート直下に作成。

version: "3.8"

services:
  db:
    image: mysql:latest
    container_name: serverless-practice-db
    environment:
      MYSQL_DATABASE: serverlessPracticeDb
      MYSQL_ROOT_PASSWORD: passw@rd
      TZ: "Asia/Tokyo"
    volumes:
      - ./docker/db/data:/var/lib/mysql
      - ./docker/db:/tmp/db
      - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    ports:
      - 3309:3306

docker/db/my.cnf作成

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
default_authentication_plugin = mysql_native_password

[client]
default-character-set=utf8mb4

default_authentication_plugin = mysql_native_passwordについて

MySQL8.0以降、認証プラグインは「caching_sha2_password」がデフォルト
・生パスワード(mysql_native_password)で認証したい場合は、my.cnfの[mysqld]にて下記の設定を行う。

[mysqld]
default_authentication_plugin = mysql_native_password

DB情報を環境定義ファイルに追加

先ほど作成したserverless-config/env/env.offline.ymlに、DB情報を追加する。

NODE_ENV: ${self:provider.stage}

// S3
S3_ENDPOINT: http://localhost:4569
S3_BUCKET: local-bucket

// DB
DB_HOST: localhost
DB_PORT: 3309
DB_NAME: serverlessPracticeDb
DB_USER: root
DB_PASSWORD: passw@rd

テーブル作成SQLファイル作成

docker/db/create_table.sql作成。

create table if not exists sample_user (
  user_id char(15),
  user_name varchar(50),
  age int(2)
);

MySQLコンテナ起動&テーブル作成

// MySQLコンテナ起動
docker compose up -d

// コンテナログイン
docker exec -it serverless-practice-db bash -p 

// テーブル作成SQL実行
root@f8bda5a9fe02:/# cd /tmp/db
root@f8bda5a9fe02:/tmp/db# mysql --user=root --password=passw@rd < create_table.sql  serverlessPracticeDb

// データベースserverlessPracticeDb に sample_userテーブルが作成されていること
root@f8bda5a9fe02:/tmp/db# mysql --user=root --password=passw@rd serverlessPracticeDb
mysql> show tables;
+--------------------------------+
| Tables_in_serverlesspracticedb |
+--------------------------------+
| sample_user                    |
+--------------------------------+
1 row in set (0.01 sec)

src/utils/dbUtils.ts作成

・MySQLに接続しSQLを実行するための処理を追加。

import mysql from "promise-mysql";
import BlueBird from "bluebird";

export async function createDbConnection(): Promise<BlueBird<mysql.Connection>> {
  return mysql.createConnection({
    host: process.env.DB_HOST,
    database: process.env.DB_NAME,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    port: process.env.NODE_ENV == "offline" ? 3309 : 3306,
    timezone: "jst",
  });
}

src/functions/csvRead/handler.ts修正

・CSVファイルの中身をINPUTに、MySQLに対してINSERT文を実行する。

import { S3Event } from "aws-lambda";
import { Parser, parse } from "csv-parse";
import iconv from "iconv-lite";
import mysql from "promise-mysql";
import { getObjectReadStream } from "../../utils/s3Client";
import { createDbConnection } from "../../utils/dbUtils";

export const run = async (event: S3Event) => {
  if (!event.Records || event.Records.length == 0) {
    return null;
  }
  const objectKey = event.Records[0].s3.object.key;

  // create db connection
  const mysqlConnection: mysql.Connection = await createDbConnection();

  // configure how csv parser processes each row
  const parser: Parser = parse();
  parser.on("readable", () => {
    let row;
    while ((row = parser.read())) {
      const statement = `INSERT INTO sample_user VALUES(${row})`;
      console.log(`executed: ${statement}`);
      mysqlConnection.query(statement);
    }
  });
  // read csv file
  const csvReadStream = await getObjectReadStream(objectKey);
  // write stream into csv parser
  csvReadStream.pipe(iconv.decodeStream("Shift_JIS")).pipe(parser);
};

動作確認

Lambda起動

npx sls offline start

ローカルS3にCSVファイルアップロード

aws --endpoint http://localhost:4569 --profile s3local s3 cp ./tests/resources/sample_user.csv s3://local-bucket/sample_user.csv

Lambda起動中のターミナルログ

info: Stored object "sample_user.csv" in bucket "local-bucket" successfully
info: PUT /local-bucket/sample_user.csv 200 811ms 0b
offline: (λ: csvRead) RequestId: cl38pcfy6000092o05fir7azs  Duration: 1043.45 ms  Billed Duration: 1044 ms
info: GET /local-bucket/sample_user.csv 200 6ms 62b
executed: INSERT INTO sample_user VALUES('00001','Anthony',10)
executed: INSERT INTO sample_user VALUES('00002','Bryant',20)
executed: INSERT INTO sample_user VALUES('00003','Carmelo',30)

DBに反映されていること

// コンテナログイン
docker exec -it serverless-practice-db bash -p

// DBログイン
mysql --user=root --password=passw@rd serverlessPracticeDb

// 確認
mysql> select * from sample_user;
+---------+-----------+------+
| user_id | user_name | age  |
+---------+-----------+------+
| 00001   | Anthony   |   10 |
| 00002   | Bryant    |   20 |
| 00003   | Carmelo   |   30 |
+---------+-----------+------+
3 rows in set (0.00 sec)

o5. RDS作成 ※後日追記

o6. デプロイ(RDSと同じVPC内にLambda作成) ※後日追記

余談

MySQL「複数のINSERT文」「1つのINSERT文with複数VALUE」どっちが速い?

結論:1つのINSERT文with複数VALUE の方が速い。

MySQL INSERT multiple rows limit

max_allowed_packet に設定されている。

mysql> SHOW VARIABLES LIKE 'max_allowed_packet';
+--------------------+---------+
| Variable_name      | Value   |
+--------------------+---------+
| max_allowed_packet | 4194304 |
+--------------------+---------+
1 row in set (0.11 sec)

MySQL my.cnf設定関連

mysql> SHOW VARIABLES like 'char%';
+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | utf8mb4                        |
| character_set_connection | utf8mb4                        |
| character_set_database   | utf8mb4                        |
| character_set_filesystem | binary                         |
| character_set_results    | utf8mb4                        |
| character_set_server     | utf8mb4                        |
| character_set_system     | utf8mb3                        |
| character_sets_dir       | /usr/share/mysql-8.0/charsets/ |
+--------------------------+--------------------------------+
8 rows in set (0.00 sec)

mysql> SET character_set_results = utf8mb4;

serverless.ymlチートシート

serverless.yml
service: serverless-practice

frameworkVersion: '3'

// プロバイダ(AWS/GCP/Azure)関連の設定
provider:
  name: aws
  runtime: nodejs14.x
  region: ${opt:region, 'us-east-1'}
  stage: ${opt:stage, 'offline'}
  endpointType: private
  // process.env.XXXX で参照したい変数をここに定義する。
  environment: ${file(serverless-config/env/env.${self:provider.stage}.yml)}
  iam.role
  versionFunctions 
  tags
  s3

plugins:
  // ---.tsをコンパイル対象にする
  - serverless-plugin-typescript
  // ローカル環境でLambda動作確認用
  - serverless-offline-lambda
  - serverless-offline
  // ローカル環境でS3動作確認用
  - serverless-s3-local

custom:
  // serverless-s3-local用の情報
  // ./tmpで http://localhosst:4568 でS3をホストする
  s3:
    host: localhost
    port: 4569
    directory: ./tests/resources

// CloudFormationで作成したいAWSリソースを定義する。
// resources配下に定義するのは、CloudFormation templateそのもの。
resources:
  Resources:
    NewResource:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: local-bucket

// Lambda関数
functions:
  csvRead:
    handler: src/functions/csvRead/handler.run 
    events:
      - s3:
          bucket: local-bucket
          event: s3:ObjectCreated:*

S3バケット関連の設定

provider.s3.HogeBucketに定義可能ならプロパティは公式ドキュメントに一覧化されている。

serverless.yml
# バケットに設定可能なプロパティは
provider:
  s3:
    fileBucket:
      name: ${self:custom.s3.bucketName}
      bucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              KMSMasterKeyID: ${self:custom.conf.KMS_KEY_ARN}
              SSEAlgorithm: 'aws:kms'
      publicAccessBlockConfiguration:
        BlockPublicAcls: true
        IgnorePublicAcls: true
        BlockPublicPolicy: true
        RestrictPublicBuckets: true
      loggingConfiguration:
        DestinationBucketName: ${self:custom.conf.S3_LOGGING_BUCKET}
        LogFilePrefix: ${self:custom.s3.bucketName}-
      tags: ${file(serverless-config/tags-s3.yml)}


# serverless-s3-local用の情報
# ローカル動作用の設定(ホストURLとホストディレクトリの設定のみ)
# ./tmpで http://localhosst:4568 でS3をホストする
custom:
  s3:
    host: localhost
    port: 4569
    directory: ./tests/resources

バケット関連の設定を理解する。

プロパティ名 意味
BucketEncryption

S3について理解する

① サーバ側の暗号化
② 暗号化キータイプ
③ ブロックパブリックアクセス設定
④ バージョニング

① "サーバ側"の暗号化とは?

・S3オブジェクトを暗号化して保存する。
・暗号/復号に使う鍵は、Amazon S3に作成/管理させる/自分でKSMに鍵を作る/別で暗号化キーを用意する のいずれかの方法で用意する。

・SSE-S3(Amazon S3 が作成、管理、使用する暗号化キー)で暗号化する場合、暗号化にお金はかからない。
・SSE-S3のデフォルト暗号化設定をカスタマイズする場合は、別料金がかかる。

There are no additional fees for using server-side encryption with Amazon S3-managed keys (SSE-S3). However, requests to configure the default encryption feature incur standard Amazon S3 request charges. For information about pricing, see Amazon S3 pricing.

② 3つの暗号化キータイプ

キータイプ 特徴
SSE-S3 Amazon S3 が作成、管理、使用する暗号化キー
AES-256方式の共通鍵を使う。
SSE-KMS AWS Key Management Service (AWS KMS) で保護されている暗号化キー。
SSE-C ユーザーが暗号化キーを管理

特に要件がなければ「SSE-S3」を。
複数のバケットに対して1つの暗号鍵を利用したい場合などは「SSE-KMS」を。
他に要件があれば「SSE-C」を。

で一旦OKな気がする。

③ ブロックパブリックアクセス設定

image.png
↑の設定を理解するためには「ACL(アクセスコントロールリスト)」「バケットポリシー」を理解する必要がある。

ACL(アクセスコントロールリスト)

・バケット/オブジェクトへのアクセス権限を付与する対象をここに定義する。
・付与対象は①AWSユーザー②事前定義済みS3グループ
・付与する単位について、

対象 アクション 意味
バケット READ バケット内の全オブジェクトを「リスト/一覧化」可能
オブジェクト READ オブジェクトデータ、メタデータの参照可能
バケット WRITE バケット内にオブジェクトを作成可能
オブジェクト WRITE ※該当しない※
バケット READ-ACP バケットACLを読み込み可能
オブジェクト READ-ACP オブジェクトACLを読み込み可能
バケット WRITE-ACP バケットACLを書き込み可能
オブジェクト WRITE-ACP オブジェクトACLを書き込み可能
バケット FULL_CONTROL バケットに対するREAD/WRITE/READ_ACL/WRITE_ACLと同義
オブジェクト FULL_CONTROL オブジェクトに対するREAD/WRITE/READ_ACL/WRITE_ACLと同義

✅ ACL = 文字通りバケット/オブジェクトにアクセスさせる対象を管理できるもの。
✅ 「バケットのACL」と「オブジェクトのACL」の2つある。
ACLによるアクセス制御の管理単位はAWSユーザー」なので、ACLによるアクセス制御ではなくバケットポリシーを使うべき。

バケットポリシー

・デフォルトで「全拒否」なので、設定してあげる必要がある。
・ACLと異なり、アクセス制御の管理単位は下記のように幅広く設定可能。

AWSアカウント/ルートユーザー
IAMユーザー
IAMロール
AWSサービス

AWS「パブリックアクセスは基本的にさせないようにしよ?」

文字通り「AWSアカウントを持たない外部の人間をアクセスできる状態」が「パブリックアクセス」である。S3の基本的な考え方として「S3にあげるものはパブリックアクセスできないもの」であるので、作成されるバケット/オブジェクトがパブリックアクセスされないようにする設定/オプションがあるわけだ。

パブリックアクセスをブロックするための設定

① バケットごとの設定
image.png
② AWSアカウントが保有する全バケットに対する設定
image.png

パブリックアクセスに対する4つのブロッキング設定 -> 参考

種類 説明
BlockPublicAcls 未来にパブリックなACLに変更することを阻止する
IgnorePublicAcls 現在のパブリックなACLを無効化する
BlockPublicPolicy 未来にパブリックなポリシーに変更することを阻止する
RestrictPublicBuckts 現在のパブリックなポリシーを無効化する
【BlockPublicAcls】 新しいアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする

新しくバケット/オブジェクトを作る。
 ↓
バケット/オブジェクトに紐づくACLが存在する。
 ↓
このACLが「パブリックアクセスできるようなもの」であっても、パブリックアクセスできないようにする。
 ↓
未来に作られるパブリックなACLBlockする。

【IgnorePublicAcls】 任意のアクセスコントロールリスト (ACL) を介して許可されたバケットとオブジェクトへのパブリックアクセスをブロックする

これから新しく作るバケット/オブジェクトがパブリックアクセスできるようなACLであってもパブリックアクセスできないようにする【1】に対して、【2】は対象が全て(これから新しく作る+既存のもの)となる。
 ↓
既存+未来で作られるパブリックなACL全てIgnoreする。

【BlockPublicPolicy】 新しいパブリックバケットポリシーを介して許可されたバケットとオブジェクトへのパブリックアクセスをブロックする

【BlockPublicAcls】のバケット版。バケットポリシーでパブリックアクセスできないようにする。

【RestrictPublicBuckts】 任意のパブリックバケットポリシーを介して、バケットとオブジェクトへのパブリックアクセスとクロスアカウントアクセスをブロックする

【IgnorePublicAcls】のバケット版。バケットポリシーでパブリックアクセスできないようにする。

④ バージョニング

その他MEMO

そもそも暗号鍵/共通鍵暗号方式/AESとは

暗号鍵とは「暗号化したり、元に戻したり(復号)するときに使うデータ/文字列
暗号化方式とは「暗号化のやり方(方式)」
暗号方式の1つに「共通鍵暗号方式」がある。データの送信者と受信者が持つ鍵は他者に奪われてはいけないものなので「秘密鍵暗号方式」とも呼ぶ。
共通鍵暗号方式の1つとして「AES(Advanced Encryption Standard)」が挙げられる。

serverlessによるデプロイはデフォルトでバージョニングされる→容量圧迫

serverless frameworkではデフォルトで、デプロイした全てのバージョンを保持するようになっています。

デプロイするたびに上書きされると思いきや、バージョニングされることで古いものは残りっぱなしになるわけだ。
不要ならserverless.ymlversionFunctions: falseを定義すればバージョニングされない👍

serverless.yml
provider:
  versionFunctions: false

sls deploy時に発生するError: EPERM: operation not permitted, unlinkの対処方法

原因はこのissueにあるとおり、serverless-typescript-pluginのバグ。
中間ファイルの存在がネックとなって引き起こされるバグらしいので、この中間ファイルを消した上でsls deployを実行する。

中間ファイルを削除するコマンド
rm -rf .serverless
rm -rf .build

sls deploy時に発生するs3 bucket already exists in stackの原因・対処方法

S3バケットを生成するserverelssプロジェクトとして、下記のserverless.ymlを作成した。
しかしこのserverless.ymlだと2つのS3バケットを作ってしまう。
 1つめ:NewResourceによるS3バケット作成
 2つめ:events.s3によるS3バケット作成 <-- これを知らなくてハマった 参考

serverless.yml
# CloudFormation template syntax. 
# What goes in this property is raw CloudFormation template syntax
resources:  
  Resources: 
    NewResource:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.s3.bucketName}

functions:
  importCsvData:
    handler: src/functions/importCsvData/importCsvDataHandler.runner
    events:
      - s3:
          bucket: ${self:custom.s3.bucketName}
          event: s3:ObjectCreated:*
          rules:
            - suffix: .csv

なので、こうすればOK。

serverless.yml
functions:
  importCsvData:
    handler: src/functions/importCsvData/importCsvDataHandler.runner
    events:
      - s3:
          bucket: ${self:custom.s3.bucketName}
          event: s3:ObjectCreated:*
          rules:
            - suffix: .csv

Serverless Plugin IfElse で、プロパティ定義自体を差し込めるようにする

・add, remove or change the values of attributes in the yml file.
・It works with both package and deploy commands.

Install

npm i serverless-plugin-ifelse --save-dev

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
2
Help us understand the problem. What are the problem?