SQSのメッセージをトリガーにLambdaを起動

以前、Laravelを利用してSQSメッセージを送信する方法を解説しました。こちらの続きです。

SQSメッセージを送信するだけで機能完了することはほぼ無いことだと思います。メッセージを送信した先で、何らかの処理を実行するために用います。
そこでよく使われるのが、Lambdaです。

SQSメッセージをトリガーにして、Lambdaを起動させるまでを簡潔に解説していきます。

0. GOAL

今回のGOALは、SQSのメッセージ送信をトリガーにして、Lambdaを起動させる。そして、起動したことをCloudWatchで確認。

1. Lambdaを作成

1-1. Lambda関数を新規作成

特別な設定は不要です。 今回、Python3.7 で作成していきます。

1-2. Roleを設定

Roleを設定していきます。 SQSをポーリングして、メッセージを受信できるRoleを設定します。

AWSLambdaSQSQueueExecutionRoleを追加します。

1-3. 関数のコードを設定

import json
def lambda_handler(event, context):
    for record in event['Records']:
       payload=record["body"]
       print(str(payload))

CloudWatchへ、SQSのメッセージを送信するだけなので、record["body"]  をprintする。だけの単純な処理です。

2. SQSメッセージ送信をトリガーに設定する

トリガーをLmabdaに設定します。

  • SQSを選択
  • キューを指定

キューを設定することで、キューにメッセージが受信することをトリガーにLambdaが起動されます。

CloudWatchにSQSメッセージがprintされています。確認してみて下さい。

Q. LambdaはSQSメッセージをどのように判定しているのか.

今回、SQSはメッセージを送信していません。その上で、LambdaではトリガーにSQSのキューを指定しただけです。

結論から言うと、LambdaはSQSのメッセージを定期的に確認しにいってます。これをポーリングと言います。

下記ブログで詳しく書いてありましたので、参照してみて下さい。

実は月額30円の定額課金?SQS⇒lambdaの罠

LaravelでAWS/SQSクライアントを作成し、メッセージを送信

AWSのキューイングサービスであるSQSを利用する方法を解説します。

0. AWSアカウントを作成し、SQSでキューを作成

AWSアカウントの作成方法は省略します。

SQSのキュー作成で、1点だけ注意点です。
キュータイプ標準 で作成して下さい。

1. LaravelにAWS-SDKをインストール

AWS-SDKとは、AWSとのAPI通信を簡素かしたパッケージです。LaravelでAWSを利用する際には、aws-sdk-php-laravel を利用する機会が多いかと思います。

すごい簡単にいうと、AWSとの面倒な認証やあれこれをやってくれる処理郡です。

1-1. composer.json へ aws-sdk-php-laravel を追記

composer.json

{
    "require": {
        //省略
        "aws/aws-sdk-php-laravel": "~3.0"
        //省略
    }
}

1-2. aws-sdk-php-laravel をインストール

composer update を実行する。

composer update

実行が完了すると
/vendor/aws 配下にAWS-SDKがインストールされています。

2. Laravel で SQS クライアントを作成し、メッセージを送信する。

Controllerで、SQSキューにメッセージを送信していきます。まずは、SQSのクライアントを作成します。

2-1. SqsClientを作成する

WebApiControllerのsendSqsメソッドの中で、処理を書いてきます。

<?php

namespace App\Http\Controllers;
use Aws\Sqs\SqsClient;

class WebApiController extends Controller
{
    public function sendSqs()
    {
        $config = [
            'credentials' => [
                'key' => env('AWS_SECRET_ACCESS_KEY'),
                'secret' => env('AWS_ACCESS_KEY_ID'),
            ],
            'region' => 'ap-northeast-1', // ご利用のリージョン
            'version' => 'latest',
        ];
        //SQS Clientを作成
        $sqs = SqsClient::factory($config);
    }
}

SqsClientaws-sdk-php-laravel をインストールした際に一緒に含まれています。

$sqs でSQSのClientを作成出来ました。次に $sqs を利用してメッセージを送信していきます。

2-2. SQSにメッセージを送信

上記で作成したクライアントを利用してメッセージを送信します。

<?php

namespace App\Http\Controllers;
use Aws\Sqs\SqsClient;

class WebApiController extends Controller
{
    public function sendSqs()
    {
        $config = [
            'credentials' => [
                'key' => env('AWS_SECRET_ACCESS_KEY'),
                'secret' => env('AWS_ACCESS_KEY_ID'),
            ],
            'region' => 'ap-northeast-1', // ご利用のリージョン
            'version' => 'latest',
        ];
        //SQS Clientを作成
        $sqs = SqsClient::factory($config);

        // SQSのメッセージ
        $value = "piyo";

        // 送りたいSQSのURLを指定
        $sqs_url = env('AWS_SQS_URL');

        $params = [
            'DelaySeconds' => 0,
            'MessageBody' => $value,
            'QueueUrl' => $sqs_url,
        ];

        $result = $sqs->sendMessage($params);
    }
}

上記で、SQSのキューにメッセージを送信出来ます。

Q. SQSのURLはどこにある?

$sqs_url = env('AWS_SQS_URL');

こちらに SQSのURLを設定する必要があります。

それは、キューの画面へいくと確認出来ます。

以上です。

LaravelとAPI Gatewayの通信に、アクセストークンの認証を追加

こちらの記事をベースに進めていきます。
上記の記事では、LaravelからAPIGatewayを経由してDynamoDBと通信をしております。 
ただし、上記の設定だとエンドポイントがバレると誰でもデータ取得出来てしまう状況した。 APIGatewayにアクセストークンの認証を追加することでセキュリティを高めます。

APIGatewayの通信に認証を追加する際には、Cognito, Lambdaなどで追加できます。今回は、Cognitoを利用していきます。

そして今回も、既に素晴らしい記事を書いて頂いている方がいたので、ほぼそちらを参考に進めてまいります。↓↓↓↓

Cognitoで認証されたユーザーだけがAPI Gatewayを呼び出せるオーソライザーを使ってみた

Cognitoユーザーの「アカウントステータス」をCONFIRMEDに変更するのに躓いている方は、こちらの記事を参考にして頂ければ問題ないかと思います。↓↓↓↓

【AWS】CognitoのFORCE_CHANGE_PASSWORDをCONFIRMEDに変える方法

さて、アクセストークンは発行できましたでしょうか。

aws cognito-idp admin-initiate-auth --user-pool-id CognitoユーザープールID --client-id アプリクライアントID --auth-flow ADMIN_NO_SRP_AUTH --auth-parameters USERNAME=ユーザ名,PASSWORD=パスワード {

コマンドを実行した結果の「”IdToken”:」部分です。

こちらのトークンを利用して、Laravelにアクセストークンを追加し、アクセス可能な状態にします。

.env
AWS_APIGATEWAY_ACCESS_KEY={IdTokenの部分}
Contoroller

追加する部分は1箇所.  headerにトークンを追加します。

'Authorization: '.env('AWS_APIGATEWAY_ACCESS_KEY'), 

public function index()
{
    
    $header = [
        'Content-Type: application/json',
        'Authorization: '.env('AWS_APIGATEWAY_ACCESS_KEY'), // headerにkeyを追加
    ];

        $params = [
        "OperationType" => "SCAN"
    ];

        
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, env('AWS_APIGATEWAY_URL'));
    curl_setopt($curl, CURLOPT_POST, TRUE);
    curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($params));
    curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
    
    $output= curl_exec($curl);
    $result = json_decode($output);
    curl_close($curl);

    dd($result);
}

LaravelからDynamoDBのデータ取得|Lambda×API Gateway経由

標題にある通り、LaravelからDynamoDBのデータを取得します。
その際に、AWSサービスの「Lambda」と「API Gateway」経由して取得します。

今回、かなり参考にさせて頂いた記事がこちらです。
AWS API GatewayとLambdaでDynamoDB操作

API Gateway → Lambda → DynamoDB までの部分はこちらを参考にさせて頂きつつ、
・途中でエラーで詰まった部分の紹介
・API GatewayとLaravelとの連携部分についての紹介(curlを利用します。)
をしていきます。

■前提

・Laravelインストール済み
・AWSアカウント取得済み

LaravelからDynamoDBのデータ取得の流れ

①:DynamoDBのテーブル、レコード作成
②:Lambda作成
③:API Gateway作成
④:API Gatewayテスト実施
⑤:API Gateway公開
⑥:ローカル環境(mac / ターミナル)から、API Gatewayへリクエスト
⑦:LaravelからAPI Gatewayへリクエスト
おまけ:LaravelからAPI Gatewayの通信に認証を追加

①〜⑤は、こちらの記事を参考に進めて頂けたらと想います。 AWS API GatewayとLambdaでDynamoDB操作 

⑥:ローカル環境(mac / ターミナル)から、API Gatewayへリクエスト

①~⑤で、API Gatewayテストが完了し、DynamoDBのデータが正しくResponseされているでしょうか。

■SCANを実施したい場合のコマンド

$ curl -X POST 'https://XXXXXX.execute-api.ap-northeast-1.amazonaws.com/APItest/dynamodbctrl' -d '{"OperationType":"SCAN"}' | jq

ここで自分が見誤った部分が、リクエストする際のURL部分です。
ステージエディターのURLを利用すれば問題ないのですが、階層によってURLが違うので注意して下さい。
こちらの方と同じ勘違いをして、「Missing Authentication Token」となってしまいました。 【AWS】APIGatewayのURLにアクセスするとエラー「Missing Authentication Token」が表示される

⑦:LaravelからAPI Gatewayへリクエスト

ローカル環境から、curlを利用してデータが返ってきましたか。

curlを利用して、LaravelからAPI Gatewayへリクエストして、DynamoDBのデータを取得していきます。

public function index()
{
    
    $header = [
        'Content-Type: application/json',
    ];

        $params = [
        "OperationType" => "SCAN"
    ];

        
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, env('AWS_APIGATEWAY_URL'));
    curl_setopt($curl, CURLOPT_POST, TRUE);
    curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($params));
    curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
    
    $output= curl_exec($curl);
    $result = json_decode($output);
    curl_close($curl);

    dd($result);
    }

curlはPHPに標準で含まれているので、importをする必要はありません。

env(‘AWS_APIGATEWAY_URL’)

ステージの呼び出しURLを指定して下さい。

curl_setopt($curl,CURLOPT_POSTFIELDS, json_encode($params));

連想配列から、jsonへ変換しています。

これで、dd($result);で、ターミナルからcurlしたデータと同じデータが返ってくると思います。

LaravelからAPI Gatewayの通信に認証を追加

下記にてまとめております。

ACM,ALB,Route53を活用してSSL化する.httpsのリダイレクトも.| AWS

標題の通りです。ご自身で取得したドメインをSSL化したい場合の一連の流れと作業を解説していきます。ちなみに、自分はお名前ドットコムでドメイン取得をしております。

■前提

・httpによるアクセスが出来ている環境であること.
・AWSを利用して、サーバー構築していること.

0:ACM,ALB,Route53各サービスの役割

初めに、各サービスの相互役割を解説して、全体の流れを解説していきたいと思います。

Route53の役割:

・ドメインのDNSレコードの作成
・ルーティング先をALBへ指定(ドメインアクセス後にALBへ)

ACMの役割:

・SSL化に必要な証明証の発行

ALBの役割:

・httpsアクセスに対して、ACMで発行した証明証を基にSSL化
・Target(今回はEC2インスタンス)へ、転送
・httpアクセスに対して、httpsへリダイレクト

①:ドメインの取得

ご自身でドメイン取得をして下さい。自分はお名前ドットコムでドメイン取得しております。

②:ACMで証明証発行

AWS Certificate Manager > 証明書 @ リクエスト

・「パブリック証明証をリクエスト」を選択
・完全修飾ドメイン名 (FQDN): {{ご自身で取得したドメインを指定}}

これで、証明証を発行します。

③:Route53レコードの設定

③-1:ホストゾーンの作成

Route 53 > ホストゾーン @ ホストゾーンの作成

・ドメイン名: {{ご自身で取得したドメインを指定}}
・パブリックホストゾーンを選択

これで、ホストゾーンを作成します。

③-2:NSレコードを、ドメイン購入サイトに反映

下記サイトなどで手順を参考に。

https://dev.classmethod.jp/articles/route53-domain-onamae/
https://blog.i-tale.jp/2020/04/13_02/

③-3:CNAMEレコードを、ACMからから作成

③-3-1:CNAMEレコードとは.

CNAMEは、ドメイン名と別ドメイン名をつけることができる。
つまり違うドメイン名で、同じ動きをしてくれます。

③-3-2:ACMから、Route53のCNAMEを作成

AWS Certificate Manager > 証明書 > ②で作成した証明証へ

Route53でレコード作成

③で作成している、Route53を選択して下さい。

④:ALBでSSL化 / ターゲットグループへの転送(EC2) / httpからのリダイレクト

④-1:ロードバランサーの作成

EC2 > ロードバランサー @ ロードバランサーの作成

Create Application Load Balancer

Basic configuration

名前を自由を設定にして、basic 情報を設定。

VPCを選択します。

今回のターゲットグループとなる、EC2インスタンスが設置してあるVPCを選択します。
※少なくとも2つ以上のアベイラビリティゾーンを追加して下さいとのことです。

リスナーを設定

・HTTPSプロトコル、443ポートで受けます。
・Forward to にターゲットグループを設定します。今回の場合は、EC2インスタンスを含まれるターゲットグループを指定
・secure listenerを From ACMで「②で作成したACMを指定」

セキュリティグループを設定

今回作成するセキュリティグループは、ALBへのセキュリティグループです。HTTPS・HTTPどちらもインバウンドを許可します。
※HTTPに対するリダイレクト処理は下記で紹介。

④-2:httpからのアクセスをhttpsへリダイレクト

設定方法を下記記事にて解説しています。

⑤:Route53で、Aレコードの設定

④で作成したALBを、Route53でAレコードをしていきます。

⑤-1:Aレコードとは.

ドメインをIPアドレスへ変換します。
ドメインへのリクエストが合った際のルート先を指定することができます。
今回はリクエストが合った際に、ALBを指定することで、SSL化を実行しています。

⑤-2:設定

・レコードタイプ:A
・エイリアス:オン
・ルーティング先:ALB >> ap-northeast-1(ご自身のリージョン)>>{{④で作成した、ALBを指定}}

[AWS|ALB]http→httpsへリダイレクトの設定

SSL化した後に、httpでのアクセスが可能 OR httpではアクセスが不可の状態が発生することで、httpへのアクセスを全てhttpsへリダイレクトしたい機会があると思います。 AWSのALBを利用して、設定していきます。

■前提

AWSのALBにて、ロードバランサー経由でWebサーバーへ接続をしている場合に限る。

ロードバランサーの設定

EC2 >> ロードバランサー >> リスナー

「リスナーの追加」をします。

すると下記の、追加設定画面が出てきますので。
・httpを80ポートで受けて
・リダイレクトをfull URLで設定

これで、htttp → https化へリダイレクトします。

Lambdaでselenium実行 / 空のHTMLがreturn|

Lambda(AWS) ✕ seleniumを利用して、下記の関数コードでスクレイピングを実行した。(Lambdaの設定詳細は割愛)

from selenium import webdriver
 import os
 os.environ['HOME'] = '/opt/'
 def lambda_handler(event, context):
     options = webdriver.ChromeOptions()
     options.add_argument("--headless")
     options.add_argument("--disable-gpu")
     options.add_argument("--hide-scrollbars")
     options.add_argument("--single-process")
     options.add_argument("--ignore-certificate-errors")
     options.add_argument("--window-size=880x996")
     options.add_argument("--no-sandbox")
     options.add_argument("--homedir=/tmp")
     options.binary_location = "/opt/headless/python/bin/headless-chromium"
     #headless-chromiumへのPATHは、ご自身の環境に応じて設定して下さい。

    browser = webdriver.Chrome("/opt/headless/python/bin/chromedriver", options=options)
    #choromedriverへのPATHは、ご自身の環境に応じて設定して下さい。

    browser.get("https://www.google.com/")
    title = browser.title
   
    print(title)
    print(browser.page_source)    
   
    browser.close()
    return 'ok'

すると、printしたかったtitleが空白。
page_sourceが下記のように空っぽのHTMLで返ってきた。

<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body></body></html>

解決方法

optionに下記を追加して下さい。

options.add_argument('--disable-dev-shm-usage')

どうやら、Chromeがメモリ不足でクラッシュしてしまっているようです。(Lambdaの設定でメモリは十分に高くしていても関係ないようです。)

メモリ保持場所を/dev/shmの代わりに/tmpディレクトリを使うことができる設定です。

参考;

https://qiita.com/yoshi10321/items/8b7e6ed2c2c15c3344c6

追加したコード

from selenium import webdriver
 import os
 os.environ['HOME'] = '/opt/'
 def lambda_handler(event, context):
     options = webdriver.ChromeOptions()
     options.add_argument("--headless")
     options.add_argument("--disable-gpu")
     options.add_argument("--hide-scrollbars")
     options.add_argument("--single-process")
     options.add_argument("--ignore-certificate-errors")
     options.add_argument("--window-size=880x996")
     options.add_argument("--no-sandbox")
     options.add_argument("--homedir=/tmp")
     options.add_argument('--disable-dev-shm-usage')
     options.binary_location = "/opt/headless/python/bin/headless-chromium"
     #headless-chromiumへのPATHは、ご自身の環境に応じて設定して下さい。

    browser = webdriver.Chrome("/opt/headless/python/bin/chromedriver", options=options)
    #choromedriverへのPATHは、ご自身の環境に応じて設定して下さい。

    browser.get("https://www.google.com/")
    title = browser.title
   
    print(title)
    print(browser.page_source)    
   
    browser.close()
    return 'ok'

【最小労力】AWS Lambdaの関数で、S3へファイルアップロードする方法

Lambda → S3 へファイルアップロードをする方法をコンパクトにお伝えします。 連携できるを確認したい、通信方法を理解したい、といった場合には最小労力での動かし方を把握しましょう。

S3、Lambdaそれぞれのサービスを設定していきましょう。

①:S3で、ファイルアップロード先のbucketを作成

S3のbucket作成しましょう。
バケット名、AWSリージョンを設定して → bucket作成

S3のbucket設定

②-1:Lambdaの関数を作成

関数の作成 >> 1から作成 を選択します。

実行ロールの部分が重要です。この後、S3にアップロードする際にS3へのアクセス権限が必要です。

赤枠で囲んだ部分が、ロールです。
後ほど、このロールにS3へのファイルアップロード可能なアクセス権限を付与していきます。

②-2:Lambdaの関数を作成:ファイルアップロードを実行するコードソース

関数のコードソースに書きを反映します。
下記部分は環境に応じて変更して下さい。
bucket = '{S3で作成したアップロード先のbucket名}'

import json import urllib.parse import boto3 import datetime def lambda_handler(event, context): try: # Get the object from the event and show its content type s3 = boto3.resource(‘s3’) bucket = ‘{S3で作成したアップロード先のbucket名}’ key = ‘test_{}.txt’.format(datetime.datetime.now().strftime(‘%Y-%m-%d-%H-%M-%S’)) file_contents = ‘S3へファイルアップロード’ obj = s3.Object(bucket,key) obj.put( Body=file_contents ) except Exception as e: print(e) raise e

②-3:Lambdaのテストイベント設定

イベント JSON部分に下記で指定した、形式を反映して下さい。

JSON イベント形式

{ “Records”: [ { “eventVersion”: “2.0”, “eventSource”: “aws:s3”, “awsRegion”: “us-east-1”, “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”: “example-bucket”, “ownerIdentity”: { “principalId”: “EXAMPLE” }, “arn”: “arn:aws:s3:::example-bucket” }, “object”: { “key”: “test%2Fkey”, “size”: 1024, “eTag”: “0123456789abcdef0123456789abcdef”, “sequencer”: “0A1B2C3D4E5F678901” } } } ] }

③:Lambdaの実行ロールにS3へのアクセス権限を付与

Lambdaの設定 >> 実行ロール へ。

先程作成した「実行ロール」が記載されています。ここからリンク遷移をして下さい。

IAM Mangerへ飛びます。

Lambdaの実行ロールとなっているロール(リンク遷移でこればそのままでOK)に対して、
アクセス許可を追加 >> ポリシーをアタッチ
から
AmazonS3FullAccess をアタッチして下さい。

④:テストで確認

Deploy → テスト を実行。
S3の指定したbucketへファイルがアップロードされています。

以上です。

今回紹介した、S3へのファイルアップロード方法だけでは、あまり使いみちがありませんが、トリガーなども組み合わせると、サーバーレスで、S3にファイル格納をすることが可能です。

EC2へPCからファイルアップロード|Linux/chmod/scp/ec2-user

タイトルの通り、EC2のディレクトリ内へローカルPCからファイルをアップロードする方法を解説します。

幾つかのSTEPで解説していきます。

①:AWS上の環境構築

ここは概ね省略します。

VPC、サブネット、セキュリティグループを設定。
サブネット上に、EC2インスタンスを起動させておく。

EC2には、sshで接続できる状態へ

②:ローカルPCからEC2へファイルアップロード(ディレクトリまるごと)

早速ですが、EC2へファイルアップロードするコマンドです。

scp -r -i ~{秘密鍵のディレクトリ}/{pemファイル名称.pem} ~{アップするローカルPCディレクトリ} ec2-user@{EC2のIPアドレス}:{EC2のアップディレクトリ}

上記のコマンドで、EC2にssh接続して、そのままディレクトリ丸ごとアップロードすることが出来ます。

※ -r のオプションは、ディレクトリ毎アップロード

ですが、アップロードするディレクトリによっては権限エラー(Permission denied)で、アップ出来ない場合があると思います。

原因は、アップロードしようとしたEC2内のディレクトリが、rootユーザーのみという場合です。

では、EC2内に入って、権限変更していきましょう。

③:EC2内のディレクトリで編集権限を変更

ディレクトリ編集 コマンド

sudo chmod {コマンドコード} {権限編集するディレクトリ} -R

※ -Rはオプションで、ディレクトリまるごとアップする場合に必要です。ファイルのみの場合は不要

アップロードする権限を付与する場合は、コマンドコード 777が良いと思います。

sudo chmod 777 {権限編集するディレクトリ} -R

で、権限を編集します。

②のコマンドを実行すると、アップロードが開始されます。

Laravelからs3に画像をUP~画像からUP編~

Laravelからs3へ画像ファイルをアップロードする情報はqiitaなどで沢山あるので、そちらを参考に頂けると良いと思います。

今回は、画像からs3へアップロードする方法を紹介します。
画像から??という方もいるかと思います。
一般的にs3のアップロードで使用する方法は、HTMLのFormからファイルをPostして、そのファイルデータ(画像が多いと思いますが)をs3へアップする方法だと思います。

この場合、 <画像>ではなくて<画像ファイル>に変換しております。

つまり、今回ご紹介するのは、
画像を一度画像ファイルへ変換して、s3にアップロードする方法です。

まずは、画像ファイルをそのままアップロードする、最小コードです。

$filename = Storage::disk('s3')->putFile('s3のファイルディレクトリ', $request->file('image'), 'public'); 

※下記実行済みが前提
・$request->file(‘image’)で、画像ファイルがpostされてくること
・.envの処理、composerでライブラリインストールなどは実行済み

上記と同じように画像をアップロードしようとします。

$filename = Storage::disk('s3')->putFile('s3のファイルディレクトリ', $image, 'public'); 

$imageは画像です。
こうすると下記のerrorが発生します。

 Command (HashName) is not available for driver (Gd). 

errorの内容は、
ファイルの形式が扱えない形式であるということです。

画像を、画像ファイル形式へ変換してあげます。

$filename = Storage::disk('s3')->putFile( 's3のファイルディレクトリ' , new File($save_path), 'public');  

$save_path は、storage上のpathです。
storageにある画像を指定しております。