[AWS] Lambdaのレイヤーサンプル

$ tree
.
└── common
├── layer
│   └── python
│   └── util.py
└── serverless.yml

serverless.yml

service: sample-layer

provider:
  name: aws

layers:
  samplelayer:
    path: layer

util.py

def hello():
	print('Hello, Lambda Layers World!')

serverless.yml

service: sample-function

provider:
  name: aws
  runtime: python3.7
  iamRoleStatements:
  - Effect: "Allow"
    Action:
    - "lambda:InvokeFunction"
    Resource: "*"

functions:
  samplefunction:
    handler: handler.handle_request
    layers:
      - {上記のLayerをsls deployした時に表示されるarn}

handler.py

import util

def handler_request(event, context):
	util.hello()

Layerはデプロイされる度にバージョンが上がる

LayersのARNを確認
$ aws lambda list-layer-versions –layer-name sample-layer –query “LayerVersions[*].LayerVersionArn”
$ aws lambda update-function-configuration –function-name testFunction3 –layers “LayersのARN”

なるほど、なんとなく仕組みと概念は理解した

[AWS] Lambdaのレイヤーとは?

Lambdaレイヤーは追加のコード、データを含むことができる.zipアーカイブ
レイヤーには、ライブラリ、カスタムランタイム、データまたは設定ファイルを含めることができる
レイヤーを使用して共有のコードとの責任の分離を促進することで、ビジネスロジックの記述を迅速に繰り返すことができる
※lambdaでサードパーティライブラリを使うにはLqyerを使う必要がある

標準ライブラリで使える場合とそうでない時がある

from typing import Tuple
import base64
from datetime import datetime, timedelta
import hashlib
import hmac
import json
import os
import random
import re
import string
import sys
import time
from typing import Any, Callable, Dict, List, Tuple
import logging

import jaconv
import openai

関数の上にLqyersがattachされる
1つのLambda関数にattachできるレイヤーは最大5つまで
=> 2つ以上のライブラリを合体させたLqyerを作成
=> 圧縮されたzipファイルのサイズが50MBを超えないこと、展開後のファイルサイズが250MBを超えないこと

### Lambda Layerの作成手順
1. 開発環境を用意
2. 開発環境のPythonバージョンをLambdaと揃える
3. Lambda Layer用のディレクトリを作成
4. 必要なライブラリやモジュールをインストール
5. ディレクトリをZIPファイルに圧縮
6. LambdaコンソールからLambda Layerを作成

– 対象ディレクトリをZIPファイルに圧縮してダウンロード
– LambdaコンソールからLambda Layerを作成

Compatible architecturesに x86_64にチェックを入れる

Lambda LayerをLambda関数に追加する

Layersから呼び出すようにコードを修正する

import json
import logging
 
import config_rules_layer
 
 
logger = logging.getLogger()
logger.setLevel(logging.INFO)
 
def check_ipv4Range_cidrs(inbound_permissions):
    for permission in inbound_permissions:
        logger.info(f'Permission: {permission}')
        if not permission['userIdGroupPairs']:
            for ipv4Range in permission['ipv4Ranges']:
                logger.info(f'ipv4Range: {ipv4Range}')
                if ipv4Range['cidrIp'] == '0.0.0.0/0':
                    logger.warning('Inbound rule includes risks for accessing from unspecified hosts.')
                    return ['NON_COMPLIANT', 'Inbound rule includes risks for accessing from unspecified hosts.']
            return ['COMPLIANT', 'No Problem.']
        else:
            return ['COMPLIANT', 'No Problem.']
 
def evaluate_change_notification_compliance(configuration_item, rule_parameters):
    try:
        config_rules_layer.check_defined(configuration_item, 'configuration_item')
        config_rules_layer.check_defined(configuration_item['configuration'], 'configuration_item[\'configuration\']')
        config_rules_layer.check_defined(configuration_item['configuration']['ipPermissions'], 'ipPermissions')
        inboundPermissions = configuration_item['configuration']['ipPermissions']
        logger.info(f'InboundPermissions: {inboundPermissions}')
        if rule_parameters:
            config_rules_layer.check_defined(rule_parameters, 'rule_parameters')
         
        if configuration_item['resourceType'] != 'AWS::EC2::SecurityGroup':
            logger.info('Resource type is not AWS::EC2:SecurityGroup. This is ', configuration_item['resourceType'], '.')
            return ['NOT_APPLICABLE', 'Resource type is not AWS::EC2:SecurityGroup.']
        
        if inboundPermissions == []:
            return ['COMPLIANT','Inbound rule does not have any permissions.']
        else:
            logger.info('check_ipv4Range_cidrs')
            return check_ipv4Range_cidrs(inboundPermissions)
 
    except KeyError as error:
        logger.error(f'KeyError: {error} is not defined.')
        return ['NOT_APPLICABLE', 'Cannot Evaluation because object is none.']
 
def lambda_handler(event, context):
    global AWS_CONFIG_CLIENT
    AWS_CONFIG_CLIENT = config_rules_layer.get_client('config', event)
 
    invoking_event = json.loads(event['invokingEvent'])
    logger.info(f'invoking_event: {invoking_event}')
    rule_parameters = {}
    if 'ruleParameters' in event:
        rule_parameters = json.loads(event['ruleParameters'])
 
    configuration_item = config_rules_layer.get_configuration_item(invoking_event)
    logger.info(f'configuration_item: {configuration_item}')
    compliance_type = 'NOT_APPLICABLE'
    annotation = 'NOT_APPLICABLE.'
 
    if config_rules_layer.is_applicable(configuration_item, event):
        compliance_type, annotation = evaluate_change_notification_compliance(
            configuration_item, rule_parameters
        )
 
    response = AWS_CONFIG_CLIENT.put_evaluations(
        Evaluations=[
            {
                'ComplianceResourceType': invoking_event['configurationItem']['resourceType'],
                'ComplianceResourceId': invoking_event['configurationItem']['resourceId'],
                'ComplianceType': compliance_type,
                'Annotation': annotation,
                'OrderingTimestamp': invoking_event['configurationItem']['configurationItemCaptureTime']
            },
        ],
        ResultToken=event['resultToken']
    )

【Python3】LambdaでS3にファイルを作成する方法

LambdaでS3にファイルを作成する方法を簡単に解説します。

(1)boto3のput_objectでS3にファイルを作成する
(2)access_key_id, secret_access_key, region_nameをLambdaの環境変数で設定
(3)lambdaにdeployし、テストで実行
(4)S3で作られたファイルを確認

ソースコード

import boto3
import datetime
import os

def lambda_handler(event, context): 
	client = boto3.client(
	    's3',
	    aws_access_key_id=os.environ['aws_access_key_id'],
	    aws_secret_access_key=os.environ['aws_secret_access_key'],
	    region_name=os.environ['region_name']
	)
	dt = datetime.datetime.now()
	client.put_object(Body="This is lockfile", Bucket = "hpscript", Key = dt.strftime("%Y-%m-%d") + ".txt")

lambda入門

トリガーイベント: S3へのファイル投入 ※management console上で操作する

def lambda_handler(event, context):
    print("Lambdaが呼ばれました!")
    input_bucket = event['Records'][0]['s3']['bucket']['name']
    input_key = event['Records'][0]['s3']['object']['key']
    print("bucket =", input_bucket)
    print("key =", input_key)

testして、Deploy, cloudwatch logsで挙動を確認

import boto3

s3 = boto3.client('s3')

def read_file(bucket_name, file):
    response = s3.get_object(Bucket=bucket_name, Key=file_key)
    return response[u'Body'].read().decode('utf-8')

def upload_file(bucket_name, file_key, bytes):
    out_s3 = boto3.resource('s3')
    s3Obj = out_s3.Object(bucket_name, file_key)
    res = s3Obj.put(Body = bytes)
    return res

def lambda_handler(event, context):
    print("Lambdaが呼ばれました!")
    input_bucket = event['Records'][0]['s3']['bucket']['name']
    input_key = event['Records'][0]['s3']['object']['key']
    print("bucket =", input_bucket)
    print("key =", input_key)

text = read_file(input_bucket, input_key)
output_key = "outputs/" + input_key
upload_file(input_bucket, output_key, bytes(text, 'UTF-8'))

なるほど、lamdbaの基本機能はなんとなく理解した

[AWS] Lambdaの基本機能

tutorialの”Introduction to AWS Lambda”で学んでいきます。

S3でbucketを作ります。
images-1357-4563
images-1357-4563-resized

HappyFace.jpeg

bucketのimages-1357-4563にuploadします。
Lambdaのcreate function

function name: Create-Thumbnail
runtime: Python 3.7 *tutorialはpython3.8で実行するとエラーになります。
Permissions: use an existing role
L lambda execution role

Add Trigger
S3
bucket: images-1357-4563
event type: All object create events

upload file
https://s3-us-west-2.amazonaws.com/us-west-2-aws-training/awsu-spl/spl-88/2.3.15.prod/scripts/CreateThumbnail.zip

Runtime setting: CreateThumbnail.handler

Test event: New event
template: s3-put

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-west-2",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "images-1357-4563",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::images-1357-4563"
        },
        "object": {
          "key": "HappyFace.jpeg",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}

Execution result: succeeded
S3で確認するとリサイズされていることがわかります。

monitor, cloudwatchで確認できます。

add triggerは基本的にAWSでのサービスなんですね。

一周回って動きの流れは理解したが、アプリケーション側のイベントとは概念が異なるようです。
インフラで何か起きた時に、サーバレスでtriggerから実行するってことか。
数年前にも同じことやったが過去と比べて理解度が違ってる。

Laravel 6.xでS3への保存・呼び出し方法

– hasManyの1M以下のファイルを、storage保存から、S3保存に切り替えたい
### 現状:storage保存

if($file = $request->file('file')){
            $path = $file->getClientOriginalName();
            $file->storeAs('./public/files/tmp/', $path);
            $inputs['path'] = $path;
        } else {
            $inputs['path'] = '';
        }

## 手順
### 1. AWSマネージメントコンソールログイン
https://ap-northeast-1.console.aws.amazon.com/
ユーザ名メニュー 「My Security Credentials」 -> IAMページ表示

### 2. S3用のIAM作成
Users -> Add user
1ページ目
– User name: [ ${project name} ]
– Select AWS access type: check [Programmatic access] ※Enables an access key ID and secret access key for the AWS API, CLI, SDK, and other development tools.

2ページ目
– Set permissions: [Attach existing policies directly]
— [AmazonS3FullAccess]

3ページ目
– Add tags (optional): none

4ページ目
– Review: nothing to do

5ページ目
– User name: ${project name}
– Access key ID : 新規発行
– Secret access key : 新規発行

### 3. S3 bucket作成
https://s3.console.aws.amazon.com/s3/home?region=ap-northeast-1
1ページ目
– Bucket name: [ ${project name} ]
– Region: [Asia Pacific (Tokyo)]

2ページ目
– Properties : 必要に応じて設定

3ページ目
– Block public access : off

4ページ目
– review -> create bucket

### 4. composerインストール
https://readouble.com/laravel/6.x/ja/filesystem.html#driver-prerequisites

// out of memoryとなるのでswapメモリを追加
$ sudo /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
$ sudo /sbin/mkswap /var/swap.1
$ sudo /sbin/swapon /var/swap.1
$ free

// s3パッケージインストール
$ php composer.phar require league/flysystem-aws-s3-v3 ~1.0
// キャッシュアダプタインストール(公式ドキュメントに絶対に必要と書いてある。。)
$ php composer.phar require league/flysystem-cached-adapter ~1.0

### 5. envファイルにS3アクセス情報追記

AWS_ACCESS_KEY_ID=${access_key}
AWS_SECRET_ACCESS_KEY=${secrete_access_key}
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=${bucket name}

### 6. controllerから保存
putFileAs(‘S3ディレクトリ’, $file, ‘filename’, ‘public’) で保存。
ファイル名を気にしない場合はputFileでもOK

if($file = $request->file('file')){
            $path = $file->getClientOriginalName();
            Storage::disk('s3')->putFileAs('/', $file, $path,'public');
            $inputs['path'] = $path;
        } else {
            $inputs['path'] = '';
        }

### 7. S3ファイルの表示

Route::get('/read', function(){
	$path = Storage::disk('s3')->url('image.jpeg');
	return "<img src=".$path.">";
});

思ったより簡単でワロタ。ベタがきだと、もう少しコードが必要だった記憶があります。

Amazon S3のAWS署名バーション2の廃止

S3の署名バージョン2が廃止になる。

– 以前の AWS リージョンの一部では、Amazon S3 で署名バージョン 4 と署名バージョン 2 がサポート。ただし、署名バージョン 2 は廃止され、署名バージョン 2 の最終サポートは 2019 年 6 月 24 日に終了
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingAWSSDK.html#UsingAWSSDK-sig2-deprecation

2016年5月以前にリリースされた AWS SDK を使用する場合、次の表に示すように、署名バージョン 4 のリクエストが必要になることがある
AWS CLI

$ aws configure set default.s3.signature_version s3v4
$ aws configure set profile.your_profile_name.s3.signature_version s3v4

Java SDK

System.setProperty(SDKGlobalConfiguration.ENABLE_S3_SIGV4_SYSTEM_PROPERTY, "true");
-Dcom.amazonaws.services.s3.enableV4

JavaScript SDK

var s3 = new AWS.S3({signatureVersion: 'v4'});

PHP SDK

									
$s3 = new S3Client(['signature' => 'v4']);

Python-Boto SDK

[s3] use-sigv4 = True

Ruby SDK

s3 = AWS::S3::Client.new(:s3_signature_version => :v4)
s3 = Aws::S3::Client.new(signature_version: 'v4')

.NET SDK

AWSConfigsS3.UseSignatureVersion4 = true;

つまり、AWS SDKを使っている場合は、バージョンを確認
– AWS SDK for Java v1 →Java 1.11.x あるいは v2 in Q4 2018 にアップグレード。
– AWS SDK for Java v2 ※アップグレード不要
– AWS SDK for .NET v1 →3.1.10 以降にアップグレード
– AWS SDK for .NET v2 →3.1.10 以降にアップグレード
– AWS SDK for .NET v3 ※アップグレード不要
– AWS SDK for JavaScript v1 →主要バージョン V3 in Q3 2019 にアップグレード
– AWS SDK for JavaScript v2 →2.68.0 以降にアップグレード
– AWS SDK for JavaScript v3 →主要バージョン V3 in Q3 2019 にアップグレード
– AWS SDK for PHP v1 →主要バージョン V3 にアップグレード
– AWS SDK for PHP v2 →主要バージョン V3 にアップグレード
– AWS SDK for PHP v3 ※アップグレード不要
– Boto2 →Boto2 v2.49.0 にアップグレード
– Boto3 →1.5.71 (Botocore)、1.4.6 (Boto3) にアップグレード
– AWS CLI →1.11.108 にアップグレード
– AWS CLI v2 ※アップグレード不要
– AWS SDK for Ruby v1 →Ruby V3 にアップグレード
– AWS SDK for Ruby v2 →Ruby V3 にアップグレード
– AWS SDK for Ruby v3 ※アップグレード不要
– Go ※アップグレード不要
– C++ ※アップグレード不要

Upload the image from the php form to s3

It is necessary to make access right to s3 bucket beforehand with IAM.
Use php library to upload to s3. Here is code.

require 'vendor/autoload.php';

if(file_exists($_FILES['upfile']['tmp_name'])){
	$ext = substr($_FILES['upfile']['name'], strrpos($_FILES['upfile']['name'],'.') + 1);
	echo $ext."<br>";
	if(strtolower($ext) !== 'png' && strtolower($ext) !== 'jpg' && strtolower($ext) !== 'jpeg' && strtolower($ext) !== 'gif'){
		echo '画像以外のファイルが指定されています。画像ファイル(png/jpg/jpeg/gif)を指定して下さい';
		exit();
	}

	$tmpname = str_replace('/tmp/', '', $_FILES['upfile']['tmp_name']);
	echo $tmpname;
	$s3client = new Aws\S3\S3Client([
			'credentials' => [
					'key' => '',
					'secret' => ''
			],
			'region' => 'ap-northeast-1',
			'version' => 'latest',
	]);

	$result = $s3client->putObject([
			'Bucket' => 'zeus-image',
			'Key' => 'test.png',
			'SourceFile' => $_FILES['upfile']['tmp_name'],
			'Content-Type' => mime_content_type($_FILES['upfile']['tmp_name']),
	]);

}


?>
<div id="content">
<h2>画像管理</h2>
<hr>
<form action="#" method="POST" enctype="multipart/form-data">
<div id="drag-drop-area">
 <div class="drag-drop-inside">
  <p class="drag-drop-info">ここにファイルをアップロード</p>
  <p>または</p>
  <!-- <input type="file" value="ファイルを選択" name="image"> -->
  <p class="drag-drop-buttons"><input id="fileInput" type="file" value="ファイルを選択" name="upfile"></p>
      <input type="submit" value="送信">
   </div>
  </div>
</form>

HTML form view

s3

You can confirm that it is being uploaded properly.

S3にアップロードしてmysqlに登録:S3 / IAM編

まず、awsコンソールにログインし、S3でバケットを作成します。適当に[zeus-image]としておきましょう。リージョンはアジアパシフィック東京でいいでしょう。

続いてプロパティ
バージョニング、アクセスログの記録、タグ、オブジェクトレベルのログ記録、デフォルト暗号化、cloudWatchリクエストメトリクス、いずれも特に設定はしません。

続いてアクセス権
デフォルトではチェックボックスに全てチェックが入っています。


このバケットのパブリックアクセスコントロールリスト (ACL) を管理する
– 新規のパブリック ACL と、パブリックオブジェクトのアップロードをブロックする (推奨)
– パブリック ACL を通じて付与されたパブリックアクセスを削除する (推奨)

このバケットのパブリックバケットポリシーを管理する
– 新規のパブリックバケットポリシーをブロックする (推奨)
– バケットにパブリックポリシーがある場合、パブリックアクセスとクロスアカウントアクセスをブロックする (推奨)


このまま作成します。

IAMでユーザ追加
ユーザを追加していきます。

アクセスの種類で「プログラムによるアクセス」にチェックを入れます。

アクセスキー IDとシークレットアクセスキーを保存します。

続いて、対象ユーザからインラインポリシーの追加を押下します。

s3の指定bucketへのアクセス付与をします。

うおー、なんかやる気がでねーぞ。
続いてcomposerを入れていきます。

[vagrant@localhost app]$ curl -sS https://getcomposer.org/installer | php
All settings correct for using Composer
Downloading…

Composer (version 1.8.0) successfully installed to: /home/vagrant/local/app/composer.phar
Use it: php composer.phar

[vagrant@localhost app]$ php composer.phar install aws/aws-sdk-php
Invalid argument aws/aws-sdk-php. Use “composer require aws/aws-sdk-php” instead to add packages to your composer.json.

あれ??

S3にアップロードしてmysqlに登録

やりたいこと
– Laravelから画像をS3にアップロードしてmysqlにパスを登録

-> その為には?
– ブレークダウンして考える
– まず、s3に画像をアップロード
– その後、ユーザID、s3のバケットのパス、ファイル名、ファイルサイズ、ステータスをmysqlに登録する
– ユーザIDに紐づいた画像を画像管理の画面で表示させる

アップロードのHTML、JSまではできている。

<form action="#" method="POST" enctype="multipart/form-data">
        <div id="drag-drop-area">
         <div class="drag-drop-inside">
          <p class="drag-drop-info">ここにファイルをアップロード</p>
          <p>または</p>
          <!-- <input type="file" value="ファイルを選択" name="image"> -->
          <p class="drag-drop-buttons"><input id="fileInput" type="file" value="ファイルを選択" name="image"></p>
              <!-- <input type="submit" value="送信"> -->
           </div>
          </div>
            

      <div class="button_wrapper remodal-bg">
         <button type="submit" value="送信" id="square_btn" onClick="location.href='#modal'">登録</button>
      </div>
       </form> 

ログイン機能は後から作ろうかなと悠著に考えていたが、先に作った方が良いのかな。。
まずは、S3へのアップロードからやりましょう。