ハッカソン参加時の備忘録 ～ 第５回：【Python】AWS LambdaとAWS S3を連携させてみよう ～

こんにちは。ITエンジニアのきゅうです。
今回作成するアプリのシステム構成図です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/57c48beb-18ca-4f57-0562-176c1bff3ef4.png)


今までインターフェース部分としてLINEを使用しておりましたが、動かすだけであれば、LambdaにTest機能がありますので、そちらを用いて動かしたいと思います。
ですが、もちろん今までお伝えしていたLINEの設定を行えば、LINEから起動させることももちろん可能です。

# Amazon S3について
では、S3とはなんぞや？という方の為に、簡潔に説明していきましょう！

## Amazon S3について
『Amazon Simple Storage Service』を、略してS3と言います。
要するにストレージサービスのことです。
もっと簡単に言うと、ファイルサーバーですね。
（ちなみにAPサーバは、EC2です。）

勿論無料枠も御座います。
5 GB の Amazon S3 ストレージ、20,000 GET リクエスト、2,000 PUT、COPY、POST、あるいは LIST リクエスト、データ送信 100 GB を毎月利用できます（S3 Standard ストレージクラス）。

## S3に接続してみよう
ということで、早速S3に接続してみましょう。
S3にファイルを新規作成（書き込み）　⇒　読み込み　⇒　削除　を一気にやっていきたいと思います。
（ファイルの更新は「読み込み＋書き込み」と同じため、省略します。）
### １．S3を作成。
```
使用するサービス：S3
使用する機能：バスケット
```
「バケットを作成」ボタンを押下し、下記のキャプチャーのように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/1b06df4a-a885-385e-d02c-02e564db7768.png)

一番下の「バケットを作成」ボタンを押下すれば、S3の作成が完了です。

### ２．ロールを作成する
```
使用するサービス：IAM
使用する機能：ロール
```
#### ロールを設定します。
「ロールを作成」ボタンを押下し、「AWSのサービス」を選択。
ユースケースはLambdaからS3にアクセスするので、「Lambda」を選択します。
そして、「次へ」ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/cba3330a-e04d-d222-2353-f5797e768ac8.png)

#### 権限を設定します。
次に権限を設定していくのですが、こちらは前回記載しましたが、
まず検索ウィンドウで「S3」と検索し、その後「FullAccess」と「Execution」がつくものにチェックをつけましょう！！
「次へ」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/c596a5c3-375d-db03-ddf3-a60fe922893a.png)

#### 名前と設定の確認をします
名前はもちろん任意です。
内容を確認して、「ロールを作成」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/0d5123c1-a91a-5474-e0e2-56d29c4a9926.png)


### ３．Lambdaで関数を作成する。
```
使用するサービス：Lambda
使用する機能：関数
```
#### 関数を作成する
「関数の作成」ボタンを押下し、下記のキャプチャーのように設定します。

::: note warn
注意点として、「実行ロール」は上で設定したロールを選択してください。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/5a47e5e4-f1c6-9f7b-9289-b2a12fc0fdef.png)


コーディングの前に補足させて頂きます。
boto3からS3をインスタンス化する際、やり方が2種類あります。

* boto3.client('s3')
* boto3.resource('s3')

です。
違いとしては以下のようにあります。

* Client APIはレスポンスが辞書型なので必要な情報を取り出しにくい
* Client APIはHTTP API 操作との 1 対 1 のマッピングが提供される
* Resource APIの方がオブジェクト指向っぽく書ける
* Resource APIが用意されていないことがあるサービスもある

そして、色々調べてみたところ、
S3へのファイル書き込み系の処理では『Resource API』を、読み込み系の処理では『Client API』を使っているサンプルが多い様に見えましたので、今回の記事でもそのようにしたいと思います。

以下のようにソースを書き換えます。

```diff_python
-import json
-
-def lambda_handler(event, context):
-    # TODO implement
-    return {
-        'statusCode': 200,
-        'body': json.dumps('Hello from Lambda!')
-    }

+import json
+import boto3
+from datetime import datetime
+
+s3_resource = boto3.resource('s3')
+s3_client = boto3.client('s3')
+
+def lambda_handler(event, context):
+
+    ##########################################################
+
+    ###S3へデータを書き込みます。【Start】###
+    file_contents = '[{"name": "NAKAMURA","age": 80},{"name": "SATOU","age": 20},{"name": "KATOU","age": 30}]'
+    bucket = 'test-s3-2197-bucket'
+    key = 'test_s3.json'
+    bucketObj = s3_resource.Object(bucket,key)
+    response = bucketObj.put(Body=file_contents)
+    
+    print("response：")
+    print(response)
+    
+    ###S3へデータを書き込みます。【End】###
+    
+    ##########################################################
+    
+    ###S3のデータを読み込みます。【Start】###
+    #######################################
+    ###以下、本来は設定が必要だが、       ### 
+    ###書き込みの際に設定しているため、   ###
+    ###今回はコメントアウト。            ###
+    #bucket = 'test-s3-2197-bucket'     ###
+    #key = 'test_s3.json'               ###
+    #######################################
+    response = s3_client.get_object(Bucket=bucket, Key=key)
+    body = response['Body'].read()
+    
+    print("body：")
+    print(body)
+    ###S3のデータを読み込みます。【End】###
+    
+    ##########################################################
+    ###S3のファイルの削除。【Start】###
+    #s3_client.delete_object(Bucket=bucket, Key=key)
+    ###S3のファイルの削除。【End】###
+
+    return body
```

↓↓↓コピー用↓↓↓

```python
import json
import boto3

s3_resource = boto3.resource('s3')
s3_client = boto3.client('s3')

def lambda_handler(event, context):

    ##########################################################

    ###S3へデータを書き込みます。【Start】###
    file_contents = '[{"name": "NAKAMURA","age": 80},{"name": "SATOU","age": 20},{"name": "KATOU","age": 30}]'
    bucket = 'test-s3-2197-bucket'
    key = 'test_s3.json'
    bucketObj = s3_resource.Object(bucket,key)
    response = bucketObj.put(Body=file_contents)
    
    print("response：")
    print(response)
    
    ###S3へデータを書き込みます。【End】###
    
    ##########################################################
    
    ###S3のデータを読み込みます。【Start】###
    #######################################
    ###以下、本来は設定が必要だが、       ### 
    ###書き込みの際に設定しているため、   ###
    ###今回はコメントアウト。            ###
    #bucket = 'test-s3-2197-bucket'     ###
    #key = 'test_s3.json'               ###
    #######################################
    response = s3_client.get_object(Bucket=bucket, Key=key)
    body = response['Body'].read()
    
    print("body：")
    print(body)
    ###S3のデータを読み込みます。【End】###
    
    ##########################################################
    ###S3のファイルの削除。【Start】###
    #s3_client.delete_object(Bucket=bucket, Key=key)
    ###S3のファイルの削除。【End】###

    return body
```
実行は最初に記載した通り、Lambdaの『Test』機能を使っていきましょう！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/af2b5ee6-a778-78df-d099-e91f390c4862.png)

上記の『Test』を押下すると、下記のような設定画面が出てくるので、任意のイベント名を設定し、イベント共有の設定を『プライベート』、そして最後に『保存』を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/ec39dc10-6528-7232-9155-1f51a25387f2.png)


するとTestが開始されます。
上記で貼り付けたLambdaのソースが実行され、以下のようなログが出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/6791dccd-5cf8-57ee-adee-abb33c91f7f8.png)

そして、最初は何もファイルがなかったですが、関数実行後はファイルが作成されております！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618971/1ebd464e-091b-c962-edd6-71470dd18d0d.png)

# ソースコード解説

簡単にですが、ソース解説も行いたいと思います。

### インポート
```python
import boto3
```
『import boto3』はAWSのサービスを使う際に、ほぼ必須となるImportになります。
しかも、LambdaがAWSのサービスであるため、AWS Lambdaで開発をしていれば、デフォルトでインストールされている為レイヤーを追加する必要はありませんが、AzureやGoogleクラウドの場合はレイヤーの追加が必要だと思われます。
その場合は下記のようなコマンドを使って、インストールしましょう。
```
sudo pip install boto3
```
当方はよく、Cloud9というAWSのサービスからインストールするようにしております。
（お手軽で便利ですよー！！）
今度それも記事にしたいと思います。


### S3の呼び出し方法について
```python
s3_resource = boto3.resource('s3')
s3_client = boto3.client('s3')
 ```
AWSサービスを呼び出す際、2つの呼び出し方があります。
それが、「resource」メソッドを呼び出すやり方と、「client」メソッドを呼び出すやり方です。
2023年にどうやら「resource」メソッドの方は非推奨となったようですが、一応2つの違いを記載します。
<dl>
  <dt>resource</dt>
  <dd>●REST APIを呼び出すクライアント・インターフェース</dd>
  <dd>●高レイヤーのオブジェクト指向なサービスアクセスが可能</dd>
  <dt>client</dt>
  <dd>●ORMっぽくAWSリソースを操作するリソース・インターフェース</dd>
  <dd>　⇒ORMとは『オブジェクト関係マッピング』のことです。</dd>
  <dd>●低レイヤーのサービスのアクセスが可能</dd>
</dl>


### バケット名とキーについて
```python
    bucket = 'test-s3-2197-bucket'
    key = 'test_s3.json'
```
途中、bucketとkeyを指定する箇所が御座いますが、bucketはS3で設定するので分かるかと思いますが、keyというのは、今回の例だとファイル名になりますが、本来的な意味では『フォルダ名＋ファイル名』になります。
気をつけましょう！！

躓きやすい所に絞って解説しましたが、「名は体を現す」という言葉の通り、他はメソッド名を見れば解説は不要だと判断しました。
（それにハッカソンでサクッと読めて実装が出来るというのが、この備忘録の売りとしたいので、長々と書き続けるのはコンセプトと違うと思い・・・）
ということで、どうしてもわからない箇所がありましたら、コメント頂ければと思います。

 # 総括
はい。
今回は一番簡単なLambdaとS3の連結部分について、記事を記載させて頂きました。
今回記載したことは、ほかのサービスでも基本となるところなので、覚えておくと他のサービスも簡単に連結させることが出来ると思います。

::: note info
まとめ
* １．S3のバケットを作成する。
* ２．IAMでロールを作成する。
* ３．Lambdaで関数を作成する。
* ４．Lambdaのソースを改修する。

※細かな設定がありますので、本編でちゃんと確認しながら進めることが、時間短縮のコツです。
:::

次回は、AWSのAIサービスのAIサービスの一つ。
Rekoginitionに繋いでみたいと思います。

ということで、本日はここまでです。
少しでも参考になったと思いましたら、「いいね」や「ストック」を押していただけますと、今後の励みになりますので、是非とも宜しくお願い致します。

最後までお読み頂きありがとうございました。