AWS

AWS-SDK-Goを使って、ユーザーが投稿したファイルをS3から削除してみる

はじめに S3でユーザーが投稿した画像を管理している場合、ユーザーがアプリを退会した際にユーザーに関する情報、S3からもユーザーのファイルを削除する必要性があります。 S3上のプレフィックスは userID/ となっており、全体のキーはbucket/userID/fileName.jpg となっています。   問題点 AWSの制約上、bucket/userID以下にファイルが1つでもあると、 いきなりbucket/userIDを削除することはできません。 加えて、リストを取得するListObjectsでは1度に1000個のキーか取得することができず、 オブジェクトの削除を行うDeleteObjectも1度に1000個のキーまでしか指定できません。 もしユーザーが1000個以上のファイルをアップロードしている場合、 一筋縄ではいかないため工夫する必要性があります。   コード   解説 ユーザーが投稿したファイルを全て取得する [crayon-5b088f6546c19977911432/] 最初のリクエストをする際に、Delimiter: aws.String(userID + "/"),をつけてリクエストをすることで、レスポンスにIsTruncatedが含まれています。 IsTruncatedがtrueだと全てを取得できず、まだ残りのオブジェクトがある状態 falseだと全てのオブジェクトを取得できた意味をします   [crayon-5b088f6546c27252340687/] 最初のリクエストでIsTruncatedがtrueの場合、 NextMarkerがレスポンスに含まれているのでこれを次のリクエストに含めておきます。 ループの中でこれらを繰り返しておき、 IsTruncatedがfalseになった時点で終了。この関数では[]stringを返します。   オブジェクトを削除する [crayon-5b088f6546c2c132357480/] 関数全体↑   [crayon-5b088f6546c31880790168/] getAllObjectで取得したキーが入った配列の長さが1000以上だった場合、 先程述べた様に、1度のリクエストで削除できるキーが最大1000のため配列を分割する必要性があります。   [crayon-5b088f6546c34461323848/] func chunk()では1000要素以上の配列に入ったキーから1つの配列が1000個未満のキーとなるように複数の配列に分割して[][]stringを返します。   [crayon-5b088f6546c37358832122/] この分割された[][]stringをfor-rangeでループさせ、画像を削除していきます。   [crayon-5b088f6546c3c031152317/] bucket/userID 以下のファイルが0になったら、bucket/userIDを削除します   使い方 ちなみに画像数が5,000枚レベルになると、マシンのスペックにもよりますがAPIサーバーからアプリへレスポンスを返すまでに1分程度、かかってしまいとても使えたものではありません。 なので、今回はDBからユーザーの情報を削除する部分はGoroutineで制御し、 このS3から画像を削除する部分はレスポンスを返した後に実行されるようにしてみました。 [crayon-5b088f6546c3e440711016/] DBからユーザー情報を削除する部分はerrgroup(goroutine)で制御を行って、 エラー処理ができるように。全ての処理が終わったあとに、   [crayon-5b088f6546c41173684381/] WaitGroupをインクリメントしておき、wg.Wait()をせずにそのままreturn nilでレスポンスを返します。 ただ、これを行うとS3から画像を削除する部分でエラーを起こるとユーザーへ通知できなくなってしまうので、Slackへ通知するようにしてます。 (そして手作業で該当ユーザーのファイルを削除していく…)   おわり Go言語に自信が全く無い素人のコードを晒して大変恐縮していますが もし間違いや、こうした方がいいのご指摘がありましたらぜひコメントしていただけると本当に幸いです。

Lambda+S3+EC2を使用してできるだけ安くリアルタイムwebPエンコードをやってみる

S3にアップロードされる jpg画像をリアルタイムで webP にエンコードしたかったので 色々やってみた。   要件 S3 バケットには毎月1000万枚の2.2MB程度の jpg画像がアップロードされると仮定 これをアプリが引っ張る前にできるだけで速く webP にエンコードする必要性があります。 webP にエンコードする理由としては、画像の容量が小さくなることでユーザーのストレスを無くす、S3 にかかる料金を安くする、CloudFront の料金を安くするため。 ちなみに何でアプリでやらないかというと、iPhone で撮った生の画像を iPhone でエンコードを行うと、アプリがフリーズするぐらい結構重い処理だからです。致し方なし。   ① Lambda だけで全てやってみる S3 にアップロードをトリガーとして Lambda を起動して、Lambda でエンコードから S3 にアップロード、既存の jpg画像を削除を行うと、最小スペックで 22秒という脅威的な数値を叩き出します。 ちなみにメモリの使用量は 108/107MB 程度です。重いなあ… 全ての Lambda スペックで試し、一番安いスペックは 256MB でした。 これでも 11秒です。 256MB 枚数 秒数 課金 1枚辺り実行時間 無料枠 1秒辺り課金額 10000000 110000000 452.028 11 1600000 0.00000417 20000000 220000000 910.728 ちなみにひと月で 10000万枚を捌き切るにはこの Lambda 関数は並列で 42 個、必要になります。 $452 * 42 = 約210万… センキューAWS! これに CloudFront の料金も加算したらとんでもない額ですね。   ② エンコード用のサーバーを用意してみる not EC2 Lambda 関数を2つ用意し、Lambda – webP request(128MB) は Convert Server にある変換用サーバーにリクエストを送り、Convert Server がステータスコード 200 以外を返したときは、Lambda – webP encoder を呼ぶ係、Lambda – webP encoder は Convert Server  に代わってエンコードしてくれます。 Convert Server は Lambda の料金を節約するために、リクエストを受け取った瞬間に 200 を返すようにしています。サーバーにリクエストがくれば途中で天変地異や、タイミングよくサーバーが壊れる、リソース不足以外で、きちんと動作するようなコードであれば対策はできるであろうと思います。 この構成にしたところ、通常時(Lambda – webP encoderが呼ばれない)であれば、課金対象実行時間が 1,500ms となるので、Lambda だけだと { (1.5 * 10000000)…

S3をCloudFrontから配信&LambdaとWAFを組み合わせて利便性とセキュリティを確保してみる

この記事は、AWS Lambda Advent Calendar 2017 の5日目となります。 今回は S3 にある画像ファイルを CloudFront から配信をしつつ、 CloudFront のアクセスログを Lambda で解析を行って、WAF の IPリストへ追加を行って、セキュリティの担保を行ってみようと思います。   S3 を CloudFront から配信する理由 前提条件に、弊社の S3 バケットにはユーザー個人が撮影した画像がアップロードされます。 なのでセキュリティには気を置いてしっかり運用していく必要性があります。 AWS SDK を使用すれば S3 から署名付きURLが吐き出され、これにアクセスすれば画像を取得できます。が、この署名付きURLには有効期限が設定されており、アプリから使うにはちょっと面倒です。 S3 を CloudFront から配信をすると、WAF が使えるようになるので固定URLで配信しつつ、セキュリティも担保できます。   構成 ①ユーザーが CloudFront にアクセスを行う ②リクエストヘッダー等を WAF へ送る ③WAF にあるルールと照合し、結果を返す ④③がOKなら S3 から画像を持ってくる(NG なら 403 を返す) ⑤アクセスログを S3 へ保存する ⑥⑤のタイミングで、Lambda が発火する ⑦アクセスログに HTTPステータスコードが 200 以外のものがあったら、IPを WAF の IPリストに追加する Lambda 関連のアドベントカレンダーなのに、Lambda の部分を特筆するところがねぇ…   CloudFront ディストリビューションの作成 オリジンに、対象の S3 バケットを。 ログの設定も何となく分かると思います。 S3 は CloudFront からのみアクセスできるようにしておきます。 ブルートフォースアタック等で、キー(/user_id/hoge_id/file_name_.jpg みたいな)がバレないように、こうします。CloudFront + WAF を組み合わせればキーがバレる前にブラックリストに入れて、アクセスを拒否できます。   WAF web ACL の作成 AWS resource to associate には先程作成した CloudFront のディストリビューションを設定します。 ルールの作成部分では IP address で作成する。 文字列判定を使えば特定の文字列をヘッダーに載せるとアクセスできるなんて仕組みもできます。   こんな感じです。 最初に IPアドレス判定を行ってから、文字列判定のルールでヘッダー認証をします。順番が大事です。今回はなんちゃってヘッダー認証があるので、デフォルトアクションは全て 403 を返すようにしています。   Lambda スクリプトを作る 言語は好きなものを選んでください。 今回僕が書いて運用しているコードを貼っておきます。 IP_SET_ID には WAF -> IP addresses の自分で作成した空のルールにアクセスすると、URLにある /ipsets/<ここ> を指定します。…

RDS for MySQL でSELECT結果をCSVに吐き出す

機会があって調べていると一筋縄ではいかなかったので覚書。 2ヶ月ぶりの記事ですが普通に生きています。   INTO OUTFILE は使えない RDS for MySQL(Aurora) v5.6.10 では INTO OUTFILEを実行するとエラーで使えない ERROR 1045 (28000): Access denied for user 'xxxx'@'%' (using password: YES)   StackOverFlow の人気コマンドを実行するとレイアウトが崩れる [crayon-5b088f654716a135947783/] これで吐き出される CSV は見るに堪えないものができてしまう。 MySQLのバージョンだったりが関係あるのかもしれない。   mysqldump-to-csv を使う https://github.com/jamesmishra/mysqldump-to-csv [crayon-5b088f6547173157843660/] これを使うと何故かヘッダが付与されないけど そこは手動で。

作ったAMIからインスタンスを作成するとSSHのパラメーターが変わる

背景 Auto Scaling Group の起動設定の元となるAMIを作成する際に SSHのPasswordAuthentication yesとしてAMIを作成をすると 復元するときにPasswordAuthentication noに戻ってしまう問題   原因 cloud-initが原因 [crayon-5b088f65471f5805389749/] ssh_pwauth: SSHのパスワード認証の有効/無効   cloud-init について Package provides configuration and customization of cloud instance. 僕の認識があっていれば起動時に毎回実行されるinitスクリプト(例外有り) 参考: cloud-initのデフォルト挙動を徹底的に調べてまとめてみた -結果ソースコードを読んだ-

no image

Amazon CloudFront CDN を使用して WordPress を使ってみる

CDN をどれ使おうかと色々試してみる中で勉強がてらAmazon CloudFrontのCDNとWordpressを組み合わせて使ってみることにした。   CDNの種類 [table "19" not found /]     CloudFront CDN Amazon AWSのCDN、従量課金制で別ドメイン型である。 リクエスト数だけで見ると料金は安いがページの容量が大きかったりすると一気に料金が跳ね上がる。 画像の容量を抑え、CSSなどはインライン化とかしてリクエスト数を減らせば料金も抑えられると思う。 HTTPSで運用しているサイトは、HTTPSで配信する必要性がある。     CloudFrontの設定 証明書のアップロード 初めてCloudFrontを使ったわけですが証明書のアップロードが面倒くさいです。 Web上からできないのでコマンドラインから証明書をアップデートする必要性があります。 参考 http://qiita.com/n0bisuke/items/a2a7d5efdc1311dc479a なおCDN用に証明書を用意できないので今回はデフォルトの証明書を使います。   CloudFrontの設定 イメージ図的にはこんな感じであってるのだと思う。draw.io 便利〜 HTTPSでサーバーを動かしてるときには 同じくHTTPSでCDNから転送しないと主要ブラウザではエラーを吐いてレイアウトが崩れて表示されてしまいます。   General Default CloudFront Certificate (*.cloudfront.net) にチェックをつけます。 独自ドメインでCDN配信するにはまた工程が増えるのでこのまま使うことをオススメします。   Origins Originの設定です。 今回はHTTPSでしか配信を行わないので Origin Protocol Policy は HTTPS Only にします。   Behaviors 特に記述することもなく。   ログイン画面、管理画面でキャッシュさせないように上記のような設定をします。 Path Patternに関して私の環境では /wordpress を付けてます。     Behavioursの設定は上記のようになると思います。   WordPressの設定 WordPressアドレス(URL) : https://<CloudFrontから割り当てられたアドレス>/wordpress サイトアドレス : https://luispc.com とします。   これで画像を開いてみるとCloudFrontアドレスになっていると思います。