AWS Lambda(C#)からAuroraにつないでみる

Serverless Meetup Tokyo #4に参加してきたら

  • AWS SAから「ServerlessにはAuroraは不向き」的な話をされた
  • ENI作成時間が気になるなら、定期的にLambdaの暖機が必要

のような逆風にも負けずLambdaからAuroraに接続するアツい話を聞いて、無性にLambda(C#)からAuroraに接続したくなったので試してみました。

そう言えば「LambdaならC#よりnode.js/Pythonの方がオススメ」的な話も聞いたことがあるような…

開発環境

  • Mac
  • .Net Core 2.0.0 / .Net Core 1.0.5共存環境

本来Lambdaの作成には.Net Core 2.0.0は不要ですが
 ・自分の環境は1.0と2.0の共存環境で
 ・2.0.0になってdotnet restoreの振る舞いが変わった
ため、dotnetコマンドのバージョンが2.0.0の場合で記載します。

作業概要

  1. .Net Coreのインストール
  2. Auroraの作成
  3. C#コード作成
  4. デプロイ用パッケージの作成
  5. Lambdaの実行

1. .Net Coreのインストール

MicrosoftのサイトからMac用をダウンロードしてインストールします。

2.0.0
1.0.5 with SDK 1.0.4

2. Auroraの作成

AWSマネジメントコンソールからAuroraを構築します。

※留意点

  • VPC内からアクセスするため、Publicly Accessible:Noにします
  • お試しなら、インスタンスタイプは一番料金が安いdb.t2.smallがオススメです

3. C#コード作成

適当な作業ディレクトリを作成し、その配下に.csファイルを作成します。
またAuroraに接続するために、下記値を設定します。

  • クラスタエンドポイント
  • ユーザ / パスワード
  • データベース名
MyFunction.cs
using Amazon.Lambda.Core;
using MySql.Data.MySqlClient;

namespace sample_aurora
{
    public class MyFunction
    {
        const string ConnectionString = "<<クラスタエンドポイント>>;user=<<ユーザ>>;password=<<パスワード>>;port=3306;database=<<データベース名>>;SslMode=None";

        [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
        public string MyHandler(LambdaEvent lambdaEvent, ILambdaContext context)
        {
            string command = lambdaEvent.Command;
            LambdaLogger.Log("Command: " + command + "n");
            switch(command){
                case "select":
                    Select();
                    break;
                case "insert":
                    Insert();
                    break;
                case "init":
                    Init();
                    break;
                default:
                    LambdaLogger.Log("Do nothing.n");
                    break;

            }
            return "Sample function was executed.";
        }

        private void Select()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("select id from test_tbl;", connection);

            string message = "Data: ";

            using (MySqlDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    string row = $"{reader["id"]}";
                    message = message + "," + row;
                }
            }

            connection.Close();

            LambdaLogger.Log(message + "n");
        }

        private void Insert()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("insert into test_tbl values ();", connection);

            command.ExecuteNonQuery();

            connection.Close();

            LambdaLogger.Log("A record has been inserted.n");
        }

        private void Init()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("create table if not exists test_tbl (id int auto_increment primary key);", connection);

            command.ExecuteNonQuery();

            connection.Close();

            LambdaLogger.Log("A table has been created.n");
        }
    }

    public class LambdaEvent
    {
        public string Command { get; set; }
    }
}

4. デプロイ用パッケージの作成

4-1. .csprojファイルの作成

同じ作業ディレクトリに.csprojファイルを作成します。

sample_aurora.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.TestUtilities" Version="1.0.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
    <PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
    <PackageReference Include="MySql.Data.Core" Version="7.0.4-IR-191" />
  </ItemGroup>
</Project>

4-2. デプロイパッケージの作成

作業ディレクトリ内で以下コマンドを実行します。

$ dotnet restore
$ dotnet publish --output publish
$ cp ~/.nuget/packages/system.data.sqlclient/4.1.0/runtimes/unix/lib/netstandard1.3/System.Data.SqlClient.dll publish/
$ cp ~/.nuget/packages/system.diagnostics.tracesource/4.0.0/runtimes/unix/lib/netstandard1.3/System.Diagnostics.TraceSource.dll pulish/
$ cd publish
$ zip ../sample_aurora.zip *

※留意点

  • System.Data.SqlClient.dllとSystem.Diagnostics.TraceSource.dllはdotnet publishでは含まれなかったため、手動でコピーしています。(無いとLambda実行時にエラーになります)

4-3. Lamdbaのデプロイ

AWSマネジメントコンソールからLambdaを選んで、「sample_aurora.zip」をデプロイします。

  • VPC対応にします
  • Auroraに接続できるsecurity groupを指定します
  • もしくは、指定したsubnet groupがaurora側のsecurity groupで許可されていれば、lamdbaで指定するsecurity grouopはダミーで構いません

5. Lambdaの実行

AWSマネジメントコンソールからLambdaを実行します。

5-1. テーブル作成

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "init"
}

Log outputに以下出力されます。

Command: init
A table has been created.

5-2. Insert

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "insert"
}

Log outputに以下出力されます。

Command: insert
A record has been inserted.

実行した回数分テーブルにレコードが追加されます。

5-3. Select

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "select"
}

Log outputに以下出力されます。

Command: select
Data: ,1,2,3

雑感

案外簡単にC# LambdaからAuroraへ接続できました。

続きを読む

aws-sam-localだって!?これは試さざるを得ない!

2017-08-11にaws-sam-localのベータ版がリリースされました。
単に「早速試したで!」と言う記事を書いても良かったのですが、少し趣向を変えてサーバレス界隈の開発環境のこれまでの推移を語った上で、aws-sam-localの使ってみた感想もお話しようかと思います。

サーバレス開発環境の今昔

サーバレス自体がかなり最近になって生まれた風潮なので昔とか言うのも問題はあるかと思いますが、とにかくサーバレスなるものの開発環境について私にわかる範囲でお話しようと思います。誤りや不正確な点については編集リクエストやコメントを頂けると幸いです。

なお、サーバレスという言葉は一般的な名詞ですが、私がAWS上でしかサーバレスに触れていないため、AzureやGCPなどには触れず、もっぱらLambdaの話になってしまうことをあらかじめご了承ください。

Lambdaのデプロイは辛かった

Lambdaの基本的なデプロイ方法はZIPで固めてアップロードです。
直接ZIPをLambdaに送るか、あらかじめS3に置いておいてLambdaにはそのURLを教えるかといった選択肢はありましたが、手動でZIPに固めてAWSに送るという手順は不可避でした。なんかもうすでに辛い。

さらに言うとLambdaに送りつけられるのはLambdaで実行するコードだけ。性質上Lambdaは単体で使われることはほとんどなく、他のサービスと連携することがほとんどなのにその辺は自分で管理するしかありませんでした。辛い。

CloudFormationで管理することは可能でしたが、CloudFormationテンプレートを書くのがかなりダルいことと、CloudFormationの更新とZIPのアップロードを別途行う必要があって手順が煩雑化しやすいため、「もうええわ」と手動管理してることが多かったと思われます。

また、ローカル環境で実行するには一工夫必要でした。

颯爽登場!Serverlessフレームワーク

そんな時に颯爽と現れたのがServerlessフレームワークでした。
ServerlessフレームワークにおいてはLambdaファンクション及び関連するリソースを独自のyamlファイルで管理します。結局は一度CloudFormationテンプレートに変換されるのですが、CloudFormationテンプレートよりも単純な形式で記述できたのが流行った一因かと思います。また、sls deployコマンドでLambdaのコードのアップロードおCloudFormationスタックの更新を一括で行ってくれたため、デプロイの手順は従来よりもはるかに簡略化されたかと思われます。

Lambdaテストしづらい問題

デプロイに関する問題はServerlessフレームワークや、ほぼ同時期に現れたSAMによって改善されましたが、開発プロセスにおいて大きな課題がありました。

テストし辛ぇ…

上記の通りLambdaは性質上他のサービスと連携することが多いため、その辺をローカル環境でどうテストするかに多くの開発者が頭を抱えました。対策として

  1. モッククラスを作って、実際のサービスのような振る舞いをさせる
  2. プロダクションとは別のリージョンに環境を再現して、そこで実行する

といった方法がありましたが、それぞれ

  1. モッククラスの実装がすこぶるダルい 下手したらロジック本体より時間かかる
  2. クラウドにデプロイしないとテストできないため、時間がかかる

といったデメリットがありました。

LocalStackとaws-sam-local

サーバレス開発者の嘆きを聞いたAtlassianがローカル環境でAWSのサービスのエンドポイントを再現するなんとも素敵なツールを作り上げました。それがLocalStackです。
再現されているサービスの数が物足りなく感じられたり、サードパーティ製であることに一抹の不安を覚えたりする人もいるかと思いますが、これ以上を求めるなら自分で作るぐらいしかないのが現状かと思います。

そしてaws-sam-local。こちらはLocalStackと少し趣が異なります。LocalStackが連携するサービスのエンドポイントを再現して提供するのに対して、aws-sam-localは実行環境の提供という意味合いが強いです。そして重要なことはAWSの公式がサポートしているということです。公式がサポートしているということです。大事なことなので(ry
「実行するのはローカルでNode.jsなりPythonなりで動かせばええやん」と思いがちですが、ランタイムのバージョンなどを本番環境と確実に揃えられるのは大きな利点です。
まだベータ版が出たばっかなので今後に期待といったところでしょう

aws-sam-local触ってみた

それでは実際に触ってみましょう。
ちなみに当方環境は

  • OS: macOS Sierra 10.12.6
  • Docker for Mac: 17.06.0-ce-mac19

です。

事前準備とインストール

公式のInstallationの項目を読み進めますが、事前にDockerを使えるようにしておく必要があります。

Macだったら普通にDocker For Macをインストールしておけば問題ありません。
一方Windowsだと

スクリーンショット 2017-08-16 14.48.18.png

まさかのDocker Toolbox推奨。 Docker For Windowsェ…
そしてaws-sam-localのインストールですが、私は-gオプション排斥論者なのでローカルインストールします

npm install aws-sam-local

実装

今回はこちらを参考にAPIゲートウェイから呼び出すLambdaを実装します。
ほぼ丸パクリですが一部アレンジしてますのでソースものっけます。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Local test
Resources:
  HelloWorld:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs6.10
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: put

ランタイムをnodejs6.10に変更してます。
新しく作る場合にわざわざ古いバージョンを使う必要もありませんので。

余談ですが、WebStormのCloudFormation用のプラグインは今の所SAMには対応してないのか、Type: AWS::Serverless::Functionのところにめっちゃ赤線を引かれます。

index.js
/**
 * Created by yuhomiki on 2017/08/16.
 */

"use strict";

const os = require("os");
console.log("Loading function");


const handler = (event, context, callback) => {
  return callback(null, {
    statusCode: 200,
    headers: { "x-custom-header" : "my custom header value" },
    body: "Hello " + event.body + os.EOL
  });
};

exports.handler = handler;

完全に書き方の趣味の問題です。
内容は参考ページのものと全く同じです。

package.json
{
  "name": "sam_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "invoke-local": "sam local invoke HelloWorld -e event.json",
    "validate": "sam validate",
    "api-local": "sam local start-api"
  },
  "author": "Mic-U",
  "license": "MIT",
  "dependencies": {
    "aws-sam-local": "^0.1.0"
  }
}

aws-sam-localをローカルインストールしているので、package.jsonのscriptsに追記しています。

実行

それでは実行してみましょう

上記のpackage.jsonに記載した

  • invoke-local
  • validate
  • api-local

を実行していきます。

invoke-local

Lambdaファンクションをローカル環境で実行します。
Lambdaファンクションに渡すevent変数はワンライナーで定義することも可能ですが、あらかじめJSONファイルを作っといた方が取り回しがいいです。

json.event.json
{
  "body": "MIC"
}

実行結果はこんな感じ

スクリーンショット 2017-08-16 15.25.42.png

まず最初にdocker pullしてランタイムに応じたDockerイメージをダウンロードします。
その後はコンテナ内でLambdaファンクションを実行し、最後にcallbackに与えた引数を出力といった流れです。
ログの形式がすごくLambdaですね。あとタイムゾーンもUTCになっていますね。
メモリの使用量をローカルで確認できるのは嬉しいですね。

-dオプションをつけることでデバッグもできるようです。
公式のgithubにはご丁寧にVSCodeでデバッグしてる様子がgifで上げられてます。

validate

テンプレートファイルのチェックをします。
デフォルトではカレントディレクトリのtemplate.yamlファイルをチェックしますが、-tオプションで変更することが可能です。

失敗するとこんな感じに怒られます。

スクリーンショット 2017-08-16 15.33.47.png

成功した時は「Valid!」とだけ言ってきます。きっと必要以上に他人に関わりたくないタイプなのでしょう。

api-local

sam local start-apiコマンドはローカル環境にAPIサーバを立ち上げます。
ホットリロード機能がついてるので、立ち上げっぱなしでもソースを修正したら自動で反映されます。いい感じですね。

スクリーンショット 2017-08-16 15.40.58.png

立ち上がるとこんなメッセージが出るので、あとはCURLなりPostManなりで煮るなり焼くなり好きにしましょう。

CURLの結果はこんな感じ
スクリーンショット 2017-08-16 15.51.39.png

所感

Lambdaのローカル実行環境を公式が用意したことに大きな意義があるかと思います。
Dockerさえあればすぐに使えることと、SAMテンプレートを書かざるをえないのでInfrastructure as Codeが自然と根付いていくのも個人的には好感を持てます。

ただし、まだベータ版なこともあって機能的にもの足りない部分があるのも事実です。
具体的にはやはりDynamoDBとかもテンプレートから読み取ってDockerコンテナで用意してくれたらなーと思います。LocalStackやDynamoDB Localでできないこともないでしょうが、DynamoDB Localに関してはテンプレートからテーブル作ったりするの多分無理なのでマイグレーション用のコードを書くことになりますし、LocalStackに関しては実はあまり真面目に使ったことないのでわかりませんが環境構築に一手間かかりそう。ていうかできれば一つのツールで完結させたい。

SAMしかりaws-sam-localしかり、AWS側としてもより開発がしやすくなるような環境づくりをしていくような姿勢を見せているので、今後のアップデートに期待したいところですね。

続きを読む

AWSのS3のデータを集計する

S3は簡単に言うとAWSのクラウドストレージサービスであり、大量にデータを保存することができます。

・Amazon S3 とは何ですか? http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/Welcome.html

今回S3上のデータを集計してグラフにして表示する必要があったため、それについてのスクリプトについて説明します。

作業環境:
macOS Sierra バージョン10.12.5
Python 2.7.10

boto

まずPythonからAWSにアクセスするためにbotoを使う必要があります。
最新バージョンはboto3なのでboto3をインストール。

$ pip install boto3

boto3をインストールしてPythonにインポートしたあと、以下のようにしてS3にアクセスします。さらに取ってきたいバケットの名前を指定してデータを取ってきます。

import boto3

s3_resource = boto3.resource('s3')
bucket = s3_resource.Bucket('bucket_name')

バケットの中にもたくさんデータがあるので、取ってきたいオブジェクトがある場合はfilterで指定します。全部必要な場合はallで取ってきます。

obj = bucket.objects.filter(Prefix='filter_word')
obj = bucket.objects.all()

オブジェクトのデータをダウンロードします。Filenameはローカルで保存するときの名前です。拡張子はS3に置いてあるファイルと同じにします。

bucket.download_file(Key=obj.key, Filename='file_name')

これでS3のデータがダウンロードできました。あとはファイルオープンし、データの形式に従ってリストや辞書を使って集計していきます。

グラフ化

次に集計したデータをグラフ化します。集計したデータは以下のような形式でテキストファイルとして保存しています。時間ごとに名前1〜6のその時の数値を記録しています。

時間1
名前1:数値1-1
名前2:数値1-2
名前3:数値1-3
名前4:数値1-4
名前5:数値1-5
名前6:数値1-6
時間2
名前1:数値2-1
名前2:数値2-2
名前3:数値2-3
名前4:数値2-4
名前5:数値2-5
名前6:数値2-6
時間3
・・・・・・・・・
・・・・・・・・
・・・・・・・
・・・・・

これをグラフ化するのにPandasとmatplotlibを使います。PandasはPyhtonでデータを扱いやすくしてくれるライブラリです。データフレーム形式にすることで簡単にグラフを作成することができます。matplotlibはグラフを描画するのに使います。どちらもpipでインストールしてきます。

データフレームは直接代入しても作れますが、辞書からも作成できるので辞書を作成してからデータフレーム形式にします。集計データのテキストファイルを開き辞書を作り、キーに名前、値に数値をリストにして入れていきます。入力し終わったらデータフレーム形式にしてグラフを作成します。

import pandas as pd

with open('data.txt')as f:
    line = f.readline()
    while line:
        results = line.rstrip()
        if ':' in results:
            data = results.split(':')
            results_dict[data[0]].append(int(data[1]))
        line=f.readline()

#辞書をデータフレームにする
my_df = pd.DataFrame.from_dict(results_dict)
#グラフの作成
my_df.plot(title='graph_title')

これでグラフが作成できました。最後にmatplotlibを使って表示します。

import matplotlib.pyplot as plt

plt.show()

その他

もっとお洒落なグラフを作成するのにseabornというライブラリが使えます。
公式ページにいろんなグラフと使い方が載っています。
https://seaborn.pydata.org/examples/index.html

続きを読む

Running Kubernetes Cluster on AWS

Following this guide https://kubernetes.io/docs/getting-started-guides/kops/, set up Kubernetes cluster on AWS using kops.

1. Install kops

According to the guide, kops is:

kops helps you create, destroy, upgrade and maintain production-grade, hig… 続きを読む

サーバーレスの入門に!自宅サーバーレス+自宅S3環境で作るサーバーレス・サムネイルサーバー!

サーバーレスのHelloWorld = サムネイルサーバー?

日本語で読めるサーバーレスの有名記事といえば,伊藤直也さんの一休の事例だと思います.
下記の記事では,Amazon Lambdaを使って,一休のサムネイルサーバーを構築した事例の紹介をしています.

伊藤直也氏が語る、サーバーレスアーキテクチャの性質を解剖する(前編)。QCon Tokyo 2016
伊藤直也氏が語る、サーバーレスアーキテクチャの性質を解剖する(中編)。QCon Tokyo 2016
伊藤直也氏が語る、サーバーレスアーキテクチャの性質を解剖する(後編)。QCon Tokyo 2016

スケールしやすい.安価にサービスできる.ということでサーバーレス・アーキテクチャは注目が集まっているようです.

そこで,今回,一休での事例をまねて,

1時間で作れる!かんたん自宅Serverless環境! ~はじめてのServerless Application入門~

で作った,Serverless環境のiron-functionsと,

コマンド1つで作れる!かんたん自宅Amazon S3互換環境!

で作った,S3互換環境を合わせて,サーバーレス・サムネイルサーバーを構築したいと思います.

サーバーレス・サムネイルサーバーのシークエンス

image.png

ざっくりとした概要の図です.
1. リクエストから「ファイル名」と「サイズ」を受け取る
2. minioのthumbsのバケットに該当のサイズのサムネイルがあるか?ない場合は3.へ.ある場合は7.へ.
3. minioのimagesのバケットからファイルをダウンロード
4. ダウンロードした画像ファイルをリサイズ
5. リサイズした画像をminioのthumbsのバケットへアップロード
6. リサイズした画像をレスポンスし,終了
7. minioのthumbsのバケットからサムネイルをダウンロード
8. ダウンロードした画像をレスポンスし,終了

API仕様書

(仕様書というほどではないですが・・・)

概要

png画像のサムネイルを提供する.

  • 指定できるサイズは画像の横のサイズのみ.
  • 縦横比は保持する.
  • png以外の画像フォーマットには非対応.
  • テストのため,エラーハンドリングは考慮しない

エントリポイント

/iron_thumbs/thumbs

URLパラメーター

変数 説明
filename imagesにアップロードされたファイル名(key)
size サムネイルの画像の横のサイズ

レスポンス

png画像データ

http://localhost:8000/iron_thumbs/thumbs?filename=test.jpg&size=30

minioのimagesバケットに入っているtest.pngを,画像の横のサイズが30pxになるように縮小し,バイナリデータとして出力する.

事前準備

今回,

  • iron-functions
  • minio

という2つのソフトウェアを使います.
手前みそですが,Seveless環境のiron-functionsは

1時間で作れる!かんたん自宅Serverless環境! ~はじめてのServerless Application入門~

の記事で紹介しています.
また,S3互換環境であるminioに関しては,

コマンド1つで作れる!かんたん自宅Amazon S3互換環境!

の記事で紹介しています.
今回,サムネイルサーバーを構築するにあたって,最低限必要なものとして,

  • docker
  • awscli
  • glide

の3種類です.glideは上記記事では,取り扱っていませんが,go言語でライブラリをインストールするためのパッケージとして今回必要です.
あとgolangの開発環境があるとよいですが,今回,動かすだけであれば必要ないと思います.(おそらく,glideのインストールと同時にgo言語の開発環境も入ると思います)

ソースコード

今回,コードが300行程度あったため,githubのほうに置きました.
https://github.com/kotauchisunsun/iron_thumbs.git

デプロイ

最初にMinio(S3互換サーバー)を前回と同じ設定で8080ポートでサービスします.

設定する変数
Access Key AKIAIOSFODNN7EXAMPLE
Secret Key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# minioを起動
$ sudo docker run --rm -p 8080:9000 -it 
    -e MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
    -e MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    minio/minio server /export

その次に,minioにバケットを作成します.

# minioへimagesバケットの作成
$ aws --endpoint-url http://192.168.0.6:8080 s3 mb s3://images
# minioへthumbsバケットの作成
$ aws --endpoint-url http://192.168.0.6:8080 s3 mb s3://thumbs

iron-functionsを8000ポートでサービスします.

# iron-functionsのサービス
$ sudo docker run --rm -it --name functions -v ${PWD}/data:/app/data 
                   -v /var/run/docker.sock:/var/run/docker.sock 
                   -p 8000:8080 iron/functions

サムネイルサーバーをサービスします.一応Dockerコンテナの流儀のようなものを意識して,各種サービスに必要なパラメーターは環境変数として渡しています.

# ソースコードをクローン
$ git clone https://github.com/kotauchisunsun/iron_thumbs.git
$ cd iron_thumbs
# パッケージの依存関係を解消
$ glide update
$ sudo fn build
$ fn apps create iron_thumbs 
    --config IMAGE_BUCKET=images
    --config THUMB_BUCKET=thumbs
    --config S3_ENDPOINT=http://192.168.0.6:8080
    --config S3_REGION=us-east-1
    --config S3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
    --config S3_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
$ fn routes create iron_thumbs /thumbs

今回,テストデータとして,いらすとやの画像を使います.それっぽい画像としてクラウドコンピューティングの画像を使います.
image.png

# 素材をダウンロード
$ wget "http://4.bp.blogspot.com/-dVbfTZcofUU/VGX8crT5GiI/AAAAAAAApG4/CB7GF5UmMqE/s400/computer_cloud_system.png"
# minioにファイルをアップロード
$ aws --endpoint-url http://192.168.0.6:8080 s3 cp computer_cloud_system.png s3://images/

実際にiron_thumbsに対してリクエストしてみます.

$ curl "http://localhost:8000/r/iron_thumbs/thumbs?filename=computer_cloud_system.png&size=180" > thumbs.png

これでサムネイルが出来ています!

ベンチマーク

「いらすとや」ベンチ

今回もベンチマークを取ってみました.

どのようなものを想定したかというと,「いらすとや」を自分で運用すると考えました.
image.png

「いらすとや」で画像を検索すると,検索にマッチした画像のサムネイル(赤枠の部分)が表示されます.この画像の横のサイズが約180pxになっています.(実際は少し違います)
このサムネイルをいかに速く作れるのか?をベンチマークしてみたいと思います.

使ったベンチマークソフトは以前も使ったApacheBench.設定は,

$ ab -n 1000 -c 10 "http://localhost:8000/r/iron_thumbs/thumbs?filename=computer_cloud_system.png&size=180"

こんな感じ.
今回,2種類のベンチマークをしました.

always_make_thumbs

これはリクエストごとに常にサムネイルを生成し,レスポンスします(ソースコードを改変してます.)

cached_thumbs

これはthumbsのバケットからサムネイルを取ってきて,レスポンスします.

サムネイルサーバーのシステム設計をする

色々なサムネイルサーバーの話がネットには転がっています.

pixivのサムネイル事情(pixiv)
料理を楽しくする画像配信システム(cookpad)

このように各社各社がしのぎを削ってるようです.
今回,このようなサムネイルサーバーの構成を考えてみました.

image.png

実運用されているサムネイルサーバーは,サムネイルの動的生成だけでなく,CDNが前に立つことがよくあるようです.
理由は主に2つあって,

  1. サムネイルサーバーの負荷軽減
  2. 高速なサムネイルの配布

です.サムネイル作成はCPUへの負荷が高く,サーバーが高負荷になりやすいです.では,サーバーを追加すればよいか?という話になるのですが,お金のないベンチャーはそういうことが出来ません.そのため,出来るだけCDNでサムネイルをキャッシュし,なおかつアクセスのある時は,サーバーレスで短時間動かすことで,安値でスケールしやすいシステムを作ることが出来ます.
実はこの辺の,サムネサーバーの要求要件が,かっちりサーバーレスのアーキテクチャにはまっているため,事例として,よく取りあげられてるのだろうなぁ.と思っています.

なのですが,

ぶっちゃけCDNとサムネサーバーとS3の面倒を見るのはめんどくさいです

キャッシュというのは,高速化には常套手段ではありますが,個人的には,バグを引き起こす原因の上位に来てます.(サーバーレスアーキテクチャだけど,すごくステートフルだし)
実際に,最近,メルカリではCDNの周りで個人流出事件を起こしてます(CDN切り替え作業における、Web版メルカリの個人情報流出の原因につきまして)
そのような理由で,個人的にはキャッシュは出来るだけ,使いたくない気持ちではあります.
しかし,今回考えたサムネサーバーの構成は,先ほどの図にあるように2段階にキャッシュを持っています.

  1. CDN
  2. サムネイル

CDNにサムネイルがなければ,サムネサーバーに問い合わせ,サムネサーバーがサムネイルを今までに作ったことがなかった場合,画像ファイルをダウンロードしてきて,サムネイルを作ります.

そのため,今回は,出来るだけキャッシュを少なくするために,「どこかのサーバーを省略できないかな?」というのが,このベンチの目的です.
そのため,

  • CDNを使わないバージョン=cached_thumbs
  • サムネを動的に作り続けるバージョン=always_make_thumbs

と考えて,ベンチを行ってみました.

結果・考察

項目 平均レスポンス時間[ms]
always_make_thumbs 8417.722
cached_thumbs 7542.938

一度サムネをS3(minio)にキャッシュする方が,1,000[ms]程度速く,7,500[ms]でサムネイルをサービスすることが出来ました

ただ50歩100歩で,普通にWebページを見ていて,7.5秒もサムネの表示に時間がかかってたら使い物にならない感じです.前に検証した,

Serverless環境は600倍以上遅い? ~GoとNode.jsとPythonでベンチマークとってみた!~

の通り,そもそもiron-functionsは遅いです.簡単なGoのサーバーレスのサービスでも6,500[ms]程度かかってました.そこから考えると,サムネ生成からレスポンスまで,8,500[ms]-6,500[ms]なので,2,000[ms],約2秒と考えられます.

あなたのサイトは2秒以内?表示速度がフォームコンバージョンに与える影響

によると,

47%のEコマースユーザーは2秒以内にページが読み込まれることを期待している

だそうです.そのため,サムネの表示に2秒程度かかっていては,やはり問題があります.そのため,前段のCDNのようなキャッシュ機構はまだまだ必要なようです.

ただ一方で

今の世の中だと2秒ぐらいでサムネイルを作れる

という見方もあります.
確かに,2秒というレスポンス時間は遅いです.これに実際にサーバーレスの起動の時間が入ると,もっと時間がかかる.Amazon Lambdaが早いといっても,レスポンスに3秒ぐらいかかるかもしれない.ただ

1人サムネイル生成を待てば,あとはCDNがキャッシュしてくれる.

という世界観だとどうでしょう.割と個人的にはありなんじゃないかなーと.1人が3秒ぐらいサムネイルの生成を待つけども,それ以外の人はCDNが高速にレスポンスしてくれる.
 今回,サムネイルサーバーを作ってみて,「結構めんどくさいなぁ.」とか「S3のバケット2つに依存するなぁ」とか,「これサムネイル側のキーの衝突とかちゃんとハンドリングするの面倒だなぁ」とか,「効率化するためにサムネをS3にキャッシュするのだるいなぁ」とか思いました.そういうことを考えると,コードをクリーンに保ちつつ,運用しやすいインフラを考えると,サーバーレス+CDNの構成で十分で,サムネイル自身をバケットに保持してキャッシュする必要はそうそうないんだなーと思いました.
確かに,

サーバレスにサムネイル画像を配信する試み

という記事の中で,

ユーザに投稿してもらった画像は、オリジナル画像としてS3に保存
オリジナル画像は配信しない (private)
サムネイル画像が要求されたらオリジナル画像から動的変換2 し、CDNで配信
サムネイル画像は保存しない
CDN にヒットしなかったらもう一度動的変換

サムネイル画像は保存しない.という方向性があって,へー.と思ってたんですが,これなら割と落としどころとしてはありなラインなんだなぁ.と検証してみて思いました.

感想

最近,後輩を指導してて,

私「この問題は,Aの可能性があります.ただ一方でBの問題も考えられ,Cの問題も考えられます.別の視点だとDの問題も考えられ・・・」
後輩「その原因のうち,どれを選んで,どれから調べればいいんですか?」
私「

という話をしました.
実際のところ,「問題のある箇所を特定するためにテストを行い」「問題を切り分け」「解決する」というのが一般的だと思います.しかし,全てが全てそういう風に出来るわけではないです.
そんな中で「えいや」と問題を割り切って,テストをしたときに,問題の原因が一発で分かる.みたいなところが「勘」と言ってる所以です.

今回,サムネイルサーバーを作ってみて,この「勘」と呼ばれる部分が働かずに苦労しました.私はGo言語に慣れていないのですが,慣れていると「普通は,はまらないところには,はまらない.」ということが出来ます.しかし,慣れていないと,「普通はこういう実装しないよな・・・」というやり方で実装してしまい,「普通は,はまらないところに,はまる」をしてしまい,問題解決にすごく時間を取られて,頭を悩ませたりしました.

あとiron-functions周りもそうでした.これは純粋にナレッジが少ない面もありますが,公式のドキュメントもなくて,厳しかったです.私はあまりDockerやDocker周りのお作法をあまり知らない面もあるので,その面で「勘」が働かず,毎度毎度厳しい感じになってました.

特に,glideによるライブラリのインストールとiron-functionsの連携部分が厳しく,GoとDockerとiron-functionsの3つの領域の重なった領域だったので,かなり時間がかかってしまいました.

そんなわけで日々組んでおかないといざ作るときになると厳しいなぁ.と思うことはよくあります.やはり日々精進が必要なのだなぁと感じた次第でした.

続きを読む

AWSCLIインストール手順

はじめに

AWSCLIを使う際にエラーがやたらに出て、詰まったのでメモ。

環境

  • Mac OS Sierra

AWS CLIのインストール


Pythonのパッケージ管理ツールを経由してawscliを入れるためPythonをインストールする。

$ brew install python

$ sudo easy_install pip

$ sudo pip install awscli

// 上記の方法で出来なかった方は以下
$ sudo pip install awscli --upgrade --ignore-installed six

AWS CLIの設定


// AWSの設定を行う
$ aws configure

上記コマンドを入力後、AWSのアクセスキーとシークレットキーを入力あとは省略してもOKです。

pemファイルの権限

sshで接続する際に、pemファイルの権限がないと怒られたので、同様の方は以下

$ chmod 600 hoge.pem

hoge.pemは任意で変更し、pemファイルまでのパスを記述すればOK

以上で完了です。

続きを読む

[JAWS-UG CLI] Amazon Kinesis Firehose re:入門 (6) 関連リソースの削除

この記事について

JAWS-UG CLI専門支部 #90 Kinesis Firehose 復習編で実施するハンズオン用の手順書です。

前提条件

必要な権限

作業にあたっては、以下の権限を有したIAMユーザもしくはIAMロールを利用してください。

  • 以下のサービスに対するフルコントロール権限

    • Kinesis Firehose
    • IAM
    • EC2
    • S3
    • CloudWatch Logs
    • STS
    • (Lambda)
      • データの変換を行う場合
    • (KMS)
      • データの暗号化を行う場合

0. 準備

0.1. リージョンを指定

オレゴンリージョンで実施します。(東京マダー?)

コマンド
export AWS_DEFAULT_REGION="us-west-2"

0.2. 資格情報を確認

コマンド
aws configure list

インスタンスプロファイルを設定したEC2インスタンスでアクセスキーを設定せずに実行した場合、以下のようになります。

結果
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************QSAA         iam-role
secret_key     ****************c1xY         iam-role
    region                us-west-2              env    AWS_DEFAULT_REGION

0.3. バージョン確認

コマンド
aws --version
結果
aws-cli/1.11.129 Python/2.7.12 Linux/4.9.38-16.33.amzn1.x86_64 botocore/1.5.92

0.4. バージョンアップ(必要に応じて)

コマンド
sudo pip install -U awscli

0.5. 変数の確認

コマンド
cat << ETX

    CF_STACK_NAME: ${CF_STACK_NAME}

ETX

1. Stackの削除

1.1. Stackの削除

Stack名の確認

コマンド
cat << ETX

    CF_STACK_NAME: ${CF_STACK_NAME}

ETX
結果

    CF_STACK_NAME: firehose-jawsug-cli

Stackの確認

コマンド
aws cloudformation describe-stacks 
    --stack-name ${CF_STACK_NAME}
結果
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-west-2:XXXXXXXXXXXX:stack/firehose-jawsug-cli/5d4aac10-7aba-11e7-bd3e-50a686be738e",
            "Description": "JAWS-UG CLI Kinesis Firehose Hands-on",
            "Parameters": [
                {
                    "ParameterValue": "XXXXXXXXXXXX_firehose_jawsug_cli",
                    "ParameterKey": "KeyPairName"
                },
                {
                    "ParameterValue": "10.0.0.0/16",
                    "ParameterKey": "VPCNetworkAddress"
                },
                {
                    "ParameterValue": "10.0.0.0/24",
                    "ParameterKey": "PublicSubnetAddr"
                }
            ],
            "Tags": [],
            "Outputs": [
                {
                    "OutputKey": "PublicIP",
                    "OutputValue": "34.209.250.207"
                },
                {
                    "OutputKey": "IAMRoleARN",
                    "OutputValue": "arn:aws:iam::XXXXXXXXXXXX:role/service-role-firehose"
                },
                {
                    "OutputKey": "S3BucketName",
                    "OutputValue": "firehose-jawsug-cli-s3bucket-1qtuo76w7zanq"
                }
            ],
            "CreationTime": "2017-08-06T15:17:30.587Z",
            "Capabilities": [
                "CAPABILITY_NAMED_IAM"
            ],
            "StackName": "firehose-jawsug-cli",
            "NotificationARNs": [],
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false
        }
    ]
}

Stackの削除

コマンド
aws cloudformation delete-stack 
    --stack-name ${CF_STACK_NAME}

Stackの削除を待機

コマンド
aws cloudformation wait stack-delete-complete 
    --stack-name ${CF_STACK_NAME}

【注意】スタックを完全に削除できない場合があります。原因を解消し再試行してください。

想定される原因として、以下の様なものが考えられます。

  • S3バケットにデータが残っている
  • IAMロールにCloudFormation以外の手段でポリシーをアタッチしている

Stackの確認

コマンド
aws cloudformation describe-stacks 
    --stack-name ${CF_STACK_NAME}
結果
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id firehose-jawsug-cli does not exist

以上

続きを読む

[JAWS-UG CLI] Amazon Kinesis Firehose re:入門 (5) Delivery Streamの削除

この記事について

JAWS-UG CLI専門支部 #90 Kinesis Firehose 復習編で実施するハンズオン用の手順書です。

前提条件

必要な権限

作業にあたっては、以下の権限を有したIAMユーザもしくはIAMロールを利用してください。

  • 以下のサービスに対するフルコントロール権限

    • Kinesis Firehose
    • IAM
    • EC2
    • S3
    • CloudWatch Logs
    • STS
    • (Lambda)
      • データの変換を行う場合
    • (KMS)
      • データの暗号化を行う場合

0. 準備

0.1. リージョンを指定

オレゴンリージョンで実施します。(東京マダー?)

コマンド
export AWS_DEFAULT_REGION="us-west-2"

0.2. 資格情報を確認

コマンド
aws configure list

インスタンスプロファイルを設定したEC2インスタンスでアクセスキーを設定せずに実行した場合、以下のようになります。

結果
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************QSAA         iam-role
secret_key     ****************c1xY         iam-role
    region                us-west-2              env    AWS_DEFAULT_REGION

0.3. バージョン確認

コマンド
aws --version
結果
aws-cli/1.11.129 Python/2.7.12 Linux/4.9.38-16.33.amzn1.x86_64 botocore/1.5.92

0.4. バージョンアップ(必要に応じて)

コマンド
sudo pip install -U awscli

0.5. 変数の確認

コマンド
cat << ETX

    DELIVERY_STREAM_NAME: ${DELIVERY_STREAM_NAME}

ETX

1. Delivery Streamの削除

1.1. Delivery Streamの削除

Delivery Stream名の確認

コマンド
cat << ETX

    DELIVERY_STREAM_NAME: ${DELIVERY_STREAM_NAME}

ETX
結果

    DELIVERY_STREAM_NAME: jawsug-cli-stream

Delivery Streamの確認

コマンド
aws firehose describe-delivery-stream 
    --delivery-stream-name ${DELIVERY_STREAM_NAME}
結果
{
    "DeliveryStreamDescription": {
        "HasMoreDestinations": false,
        "LastUpdateTimestamp": 1502033993.512,
        "VersionId": "2",
        "CreateTimestamp": 1502033075.665,
        "DeliveryStreamARN": "arn:aws:firehose:us-west-2:XXXXXXXXXXXX:deliverystream/jawsug-cli-stream",
        "DeliveryStreamStatus": "ACTIVE",
        "DeliveryStreamName": "jawsug-cli-stream",
        "Destinations": [
            {
                "DestinationId": "destinationId-000000000001",
                "ExtendedS3DestinationDescription": {
                    "RoleARN": "arn:aws:iam::XXXXXXXXXXXX:role/service-role-firehose",
                    "Prefix": "",
                    "BufferingHints": {
                        "IntervalInSeconds": 60,
                        "SizeInMBs": 1
                    },
                    "EncryptionConfiguration": {
                        "NoEncryptionConfig": "NoEncryption"
                    },
                    "CompressionFormat": "UNCOMPRESSED",
                    "S3BackupMode": "Disabled",
                    "CloudWatchLoggingOptions": {
                        "Enabled": true,
                        "LogStreamName": "S3Delivery",
                        "LogGroupName": "/aws/kinesisfirehose/jawsug-cli-stream"
                    },
                    "BucketARN": "arn:aws:s3:::firehose-jawsug-cli-s3bucket-1qtuo76w7zanq",
                    "ProcessingConfiguration": {
                        "Enabled": true,
                        "Processors": [
                            {
                                "Type": "Lambda",
                                "Parameters": [
                                    {
                                        "ParameterName": "NumberOfRetries",
                                        "ParameterValue": "3"
                                    },
                                    {
                                        "ParameterName": "LambdaArn",
                                        "ParameterValue": "arn:aws:lambda:us-west-2:XXXXXXXXXXXX:function:test-func-apache2json:$LATEST"
                                    }
                                ]
                            }
                        ]
                    }
                },
                "S3DestinationDescription": {
                    "RoleARN": "arn:aws:iam::XXXXXXXXXXXX:role/service-role-firehose",
                    "Prefix": "",
                    "BufferingHints": {
                        "IntervalInSeconds": 60,
                        "SizeInMBs": 1
                    },
                    "EncryptionConfiguration": {
                        "NoEncryptionConfig": "NoEncryption"
                    },
                    "CompressionFormat": "UNCOMPRESSED",
                    "CloudWatchLoggingOptions": {
                        "Enabled": true,
                        "LogStreamName": "S3Delivery",
                        "LogGroupName": "/aws/kinesisfirehose/jawsug-cli-stream"
                    },
                    "BucketARN": "arn:aws:s3:::firehose-jawsug-cli-s3bucket-1qtuo76w7zanq"
                }
            }
        ]
    }
}

Delivery Streamの削除

コマンド
aws firehose delete-delivery-stream 
    --delivery-stream-name ${DELIVERY_STREAM_NAME}

Delivery Streamの確認

完全に削除が完了するまで、数分かかります。しばらくお待ち下さい。

コマンド
aws firehose describe-delivery-stream 
    --delivery-stream-name ${DELIVERY_STREAM_NAME}
結果
An error occurred (ResourceNotFoundException) when calling the DescribeDeliveryStream operation: Firehose jawsug-cli-stream under account XXXXXXXXXXXX not found.

以上

続きを読む

[JAWS-UG CLI] Amazon Kinesis Firehose re:入門 (4) Kinesis Agentによるレコードの登録

この記事について

JAWS-UG CLI専門支部 #90 Kinesis Firehose 復習編で実施するハンズオン用の手順書です。

前提条件

必要な権限

作業にあたっては、以下の権限を有したIAMユーザもしくはIAMロールを利用してください。

  • 以下のサービスに対するフルコントロール権限

    • Kinesis Firehose
    • IAM
    • EC2
    • S3
    • CloudWatch Logs
    • STS
    • (Lambda)
      • データの変換を行う場合
    • (KMS)
      • データの暗号化を行う場合

0. 準備

0.1. リージョンを指定

オレゴンリージョンで実施します。(東京マダー?)

コマンド
export AWS_DEFAULT_REGION="us-west-2"

0.2. 資格情報を確認

コマンド
aws configure list

インスタンスプロファイルを設定したEC2インスタンスでアクセスキーを設定せずに実行した場合、以下のようになります。

結果
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************QSAA         iam-role
secret_key     ****************c1xY         iam-role
    region                us-west-2              env    AWS_DEFAULT_REGION

0.3. バージョン確認

コマンド
aws --version
結果
aws-cli/1.11.129 Python/2.7.12 Linux/4.9.38-16.33.amzn1.x86_64 botocore/1.5.92

0.4. バージョンアップ(必要に応じて)

コマンド
sudo pip install -U awscli

0.5. 変数の確認

コマンド
cat << ETX

    KEY_MATERIAL_FILE_NAME: ${KEY_MATERIAL_FILE_NAME}
    PUBLIC_IP_ADDRESS: ${PUBLIC_IP_ADDRESS}
    DELIVERY_STREAM_NAME: ${DELIVERY_STREAM_NAME}
    BUCKET_NAME: ${BUCKET_NAME}

ETX

1. ログイン

1.1. SSHで接続

初回接続時、yesを選択

コマンド
ssh -l ec2-user -i ~/.ssh/${KEY_MATERIAL_FILE_NAME} ${PUBLIC_IP_ADDRESS}

2. Kinesis Agentの設定

2.1. 設定ファイルの生成

設定ファイル名の指定

コマンド
AGENT_SETTING_FILE_NAME="agent.json"

デリバリーストリーム名の指定

コマンド
DELIVERY_STREAM_NAME="jawsug-cli-stream"

変数の確認

コマンド
cat << ETX

   AGENT_SETTING_FILE_NAME: ${AGENT_SETTING_FILE_NAME}
   DELIVERY_STREAM_NAME: ${DELIVERY_STREAM_NAME}

ETX
結果

   AGENT_SETTING_FILE_NAME: agent.json
   DELIVERY_STREAM_NAME: jawsug-cli-stream

設定ファイルの生成

コマンド
cat << EOF > ${AGENT_SETTING_FILE_NAME}
{
  "cloudwatch.emitMetrics": true,
  "cloudwatch.endpoint": "https://monitoring.us-west-2.amazonaws.com",
  "firehose.endpoint": "https://firehose.us-west-2.amazonaws.com",
  "flows": [
    {
      "filePattern": "/var/log/httpd/access_log",
      "deliveryStream": "${DELIVERY_STREAM_NAME}",
      "initialPosition": "START_OF_FILE"
    }
  ]
}
EOF

cat ${AGENT_SETTING_FILE_NAME}

JSONファイルの検証

コマンド
jsonlint -q ${AGENT_SETTING_FILE_NAME}

2.2. 設定ファイルの配置

既存ファイルの退避

コマンド
sudo cp /etc/aws-kinesis/agent.json /etc/aws-kinesis/agent.json.org

生成したファイルの配置

コマンド
sudo cp agent.json /etc/aws-kinesis/agent.json

権限の付与

Kinesis-Agentの実行ユーザである「aws-kinesis-agent-user」に対して、
データソースの読み取りおよび実行権限が必要です。

Writing to Amazon Kinesis Firehose Using Amazon Kinesis Agent

コマンド
sudo chmod 755 /var/log/httpd
sudo chmod 644 /var/log/httpd/*

サービスの再起動

コマンド
sudo service aws-kinesis-agent restart

ログオフ

コマンド
exit

3. 動作確認

3.1. Web Serverにアクセス

てきとうにアクセスしてみてください。

そして、1分ほど待ちます。。。

3.2. S3バケット上のファイルを確認

S3バケット上のファイルを確認

コマンド
aws s3 ls --recursive ${BUCKET_NAME}

S3 Bucket上のファイルをダウンロード

コマンド
mkdir data03
cd data03
コマンド
aws s3 sync s3://${BUCKET_NAME} .
結果
download: s3://jawsug-cli-firehose-************/2016/12/19/07/jawsug-cli-stream-1-2016-12-19-07-54-56-47a385b8-4075-44ec-bf15-4f6143b03ddb to 2016/12/19/07/jawsug-cli-stream-1-2016-12-19-07-54-56-47a385b8-4075-44ec-bf15-4f6143b03ddb

S3 Bucket上のファイルを閲覧

コマンド
cat (任意のファイル名)
結果
222.150.28.224 - - [03/Dec/2016:08:05:20 +0000] "GET / HTTP/1.1" 403 3839 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"
222.150.28.224 - - [03/Dec/2016:08:05:21 +0000] "GET / HTTP/1.1" 403 3839 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"

3.3. ファイルの削除

ファイルの削除

コマンド
aws s3 rm --recursive s3://${BUCKET_NAME}/
結果
(省略)
コマンド
cd ..
rm -r data03

以上

続きを読む