S3 Cloudfront でのサブディレクトリへアクセス Lambda@Edge

S3にて静的コンテンツをweb公開するとき、cloudfrontを噛ませると色々なメリットがるため、一般的になっていますが、その時の問題が、s3をstatic web hostingしなければ サブディレクトリがファイル指定しないと参照できない ということがあります。

https://example.com/ → https://example.com/index.html を参照してくれる(cloudfrontのDefault Root Objectの設定にて)
https://example.com/dir/ → https://example.com/dir/index.html を返してくれず403が帰ってくる

この時によくある設定方法としては、
s3でstatic web hostingをONにして、cloudfrontの向き先をS3のエンドポイントに設定するという方法がありますが、
個人的には、static web hostingをするのは、なんのために、cloudfront経由のみでしかs3にアクセスできなくしているのかという感じなので、避けたいところでした。

lambda@Edge を使う

ここで、

lambda@Edge を使うということで、どういうことかというと、
CloudFront ディストリビューションに Lambda 関数に関連付け、CloudFront エッジロケーションでリクエストとレスポンスが傍受することができ、次の CloudFront イベントの発生時に Lambda 関数を実行できます。

ここを参考にさせていただきました。ありがとうございます。

要するに、ざっくりとした流れは

  1. リクエスト
  2. Cloudfrontへのアクセス時にLambda@Edgeが呼ばれ
  3. Lambda@Edgeにて必要な処理をし、Cloudfrontに伝達
  4. Cloudfrontからs3参照
  5. レスポンス

ということで、

https://example.com/dir/

このようなアクセスが来た時に、index.htmlを付加しcloudfrontに通達してくれれば、403になることはありませんね。

Lambda関数を作成

  • AWSコンソールからlambdaを選択します
  • リージョンはバージニア北部 にします(このリージョンしかサポートしてません(2019年1月現在))
  • 必要項目を下記のように入力、選択

キャプチャです

キャプチャです

これで面倒なロールやアクセス設定はOKです。今は簡単ですね。
(Lambda@EdgeよりCloudfrontへアクセスするようのロールを作成して付与したことになります)

関数のコード

'use strict';
exports.handler = (event, context, callback) => {
    //
    // Extract the request from the CloudFront event that is sent to Lambda@Edge
    var request = event.Records[0].cf.request;
    //
    // Extract the URI from the request
    var olduri = request.uri;
    //
    // Match any '/' that occurs at the end of a URI. Replace it with a default index
    var newuri = olduri.replace(/\/$/, '\/index.html');
    //
    // Log the URI as received by CloudFront and the new URI to be used to fetch from origin
    console.log("Old URI: " + olduri);
    console.log("New URI: " + newuri);
    //
    // Replace the received URI with the URI that includes the index page
    request.uri = newuri;
    //
    // Return to CloudFront
    return callback(null, request);
    //
};

こちらをコピーして貼り付けます。

トリガーの設定

  1. 画面上部、アクションのドロップダウン
  2. 新しいバージョンを発行
  3. 表示されたダイアログにバージョンを記入(未記入でもOK)
  4. 左のトリガーからcloudfrontを選択
  5. 表示されるトリガーの設定にで、cloudfrontのdistribution IDを入れ、cloudfrontのイベントをオリジンリクエスト とします。

これで設定は完了になります。

この後Cloudfrontの指定したdistributionのstatusが In Progressとなります。

まとめ

意外と簡単に設定でき、問題を解決できたかと思います。
同じ悩みの方は是非お試しください。間違っている部分等ありましたらご指摘ください。


hugo を使って爆速でブログ・サイトを作る - AWS S3 Cloudfront Route53

新たに記事投稿型の webサイトを構築しようと思い、hexoでも良かったんですが、せっかくなら使ったことのないFrameworkを使いたいと思い、今回は、多言語対応にも強い、GO言語で書かれたhugoを使ってみることにしました。


その際の構築手順をメモっておきます。

hugoインスコ

homebrew で入れる。

brew install hugo

サイト作成

作業dirで次のコマンドで作成します

hugo new site ~/○○○○○

結構時間かかる

テーマの追加

cd ○○○○○
git init;\
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke;\

設定ファイルにテーマを記述

echo 'theme = "ananke"' >> config.toml

記事作成

下記コマンドで記事を作成

hugo new posts/my-first-post.md

posts って部分は、contents以下のdir名になるので、なんでもOKです

作成された記事のファイルは下記

content/posts/my-first-post.md

このファイルをマークダウン形式で記述します。

ローカルで確認

hugo server -D
-D オプションは、ドラフト(下書き)記事もビルドするという意味

ドラフト機能

これは、Defaultで、下記ファイルの生成されるもとのファイルにて

archetypes/default.md
draft: true

この記述により、この記事はドラフトです。という内容になります。
その場合、ビルドした際、htmlファイルは生成されません。

ドラフト記事も含めてビルドする際(あんまり考えれらない)は、-D(–buildDrafts)コマンドを使うとhtmlファイルが生成されますが、
通常、記事中の、draft: falseに変更するのがいいと思います。

hugo undraft ○○○○

のundraftコマンドはなくなりました。

またドラフト中の記事の一覧を確認するには、

hugo list drafts

で確認できます。

http://localhost:1313/

で確認できます。

多言語対応

hugo は多言語対応に大変優れていますね。
とりあえず、日本語と英語を作成してみます。下記ファイルで設定を行います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~/config.toml

DefaultContentLanguage = "en"
DefaultContentLanguageInSubdir = true
.
.
.

[Languages]
[Languages.en]
weight = 1

title = "○○○"

[Languages.ja]
weight = 2

title = "○○○"
  • DefaultContentLanguage で デフォルトの言語を設定
  • DefaultContentLanguageInSubdir を trueにすると、リクエストURLに言語ごとにpathを切ってくれます。

英語版

example.com/en/...

日本語版

example.com/ja/...

という感じです。

  • [Languages]では、それぞれのタイトルや言語ごとの必要事項を設定しておきます。使用するテーマなどに依存します。
  • weightは優先する方を低い数値にしておきます。いらないかも。

それぞれの記事を作成

1
2
3
4
5
6
7
ここでは、英語優先なので、
hugo new posts/sample.md

と、すると英語ページ

hugo new posts/sample.ja.md
で日本語ページとして読んでくれます。

/ja/へのアクセスで、xxxx.ja.md の記事が読まれ、/en/ もしくは / で、xxxx.md or xxxx.en.md の記事が読み込まれ勝手に切り替えてくれます。
あとはそれぞれの言語で記事を書くだけです。

公開

generateします

$ hugo

これだけです。そうするとpublic以下に生成されます。
これらをs3やgithubに上げればOKです。

hugoまとめ

非常にささっと簡単にwebサイトができました。またthemesもかなりたくさんありますが、それをoverride的な形でカスタマイズすることも可能です。
簡単な上に非常に柔軟性があり、hexoよりもいいかもしれません。


黒島 旅 記録 - その2

黒島

八重山諸島 黒島へ行った記録 その2日目です。

その1は黒島 旅 記録 - その1をチェックしてください。


早寝早起き

昨晩8時に就寝した僕は、5時半に起きるというおじーちゃんスタイルで一日が始まります。
宿の敷地内をプラプラしてみることに

鳥小屋
鳥小屋発見w ヤギもいましたが、写真とるの忘れました。なんのために飼っているのでしょうか?
卵とったり、乳を絞ったりするのでしょうか。わかりません。


とりあえず最寄りの牛に挨拶しに
牛

早朝でも起きてるんですね。


そんなこんなしていたら、早々と朝ごはんの時間が来たので、宿に戻る。
朝ごはんの写真取るの忘れた。
人間、早寝早起きすると朝お腹が空くんですね。すげー美味しく感じました。
帰ってからも早寝早起きを心がけよう

島を自転車で散策

今日は昨日行けなかったところに全部行ってみよう!
黒島は普通に一周するんだったら、自転車で一時間程度で回れるそうですが、時間もあるしゆっくり回ってみることにしました。

チャリ

仲本海岸

この日も天気が曇りだったので、いまいちでしたがやっぱり海はとってもきれいです。夏なら海水浴する人が多いのでしょうか
仲本海岸
仲本海岸2
仲本海岸3

こういうガイド?みたいなのもある
仲本海岸4
しかもここ黒島は竹富町のようで、竹富町の公衆無線LANが飛んでいましたw
他にもフェリー乗り場にもwifiが飛んでいたような気がします。


次はどこに行こうかな。。。適当に走る
黒島道
ずっとこんな景色でのどかだなー。
これぞ「非日常」


そういえば、黒島では車は殆ど走ってません。牛の世話をしている方がたまーに軽トラで走る程度でした。
ちょっと道をそれて奥に行ったらまたビーチがありました。どこもきれいですね。
ビーチ

黒島灯台

途中舗装されていない道を進むと、 黒島灯台 があります。でもこれ、普通の人は登れないようでした。

黒島灯台
さ、次

黒島の草原
進めど進めど、サバンナのようで広大な自然が広がります。いたるところに牛。

黒毛和牛たち

そういえば人にあってないw 牛にはもう多分100等以上は合ってるのに。
牛
牛2

牛3
やっぱり牛の島ですね。


牛4


ひたすら草食ってます。

海外のペットボトル

どこらへんか地図では忘れましたが、ぶっ壊れた短い桟橋みたいながありました。
桟橋
流れ着いたゴミがすごかった。。。

よくよくゴミを見るとペットボトルが見たことないやつばかりで、ハングルだか漢字だか。
ペットボトル
どこの海もやっぱりゴミとかは流れ着くものですね。
僕の住む伊豆白浜も台風の後などは、いろんなゴミが流れ着きます。

市街地

市街地と言っても、小さな島です。商店が一つと、診療所、公民館みたいな?飲食店もあるんでしょうか。
街
写真取るの忘れましたが、商店で、オリオンビールと、スパムおにぎりをゲットし、桟橋へ

伊古桟橋

伊古桟橋
糞きれいでしたw 雲がなかったらもっと最高なのでしょう
伊古桟橋2
商店でゲットしたオリオンとスパムおにぎりをここで食す。
おにぎりのノリが地面とどうかしてるw


ここ最高に気持ち良かったです。
伊古桟橋3
水キレイすぎ。

のどかな風景

途中舗装されている以外の道もたくさん自転車で走りました

道
道
自転車で全然走れます。土に埋もれている牛も発見
埋もれている牛
お風呂に入っているみたい

黒島研究所

ここには、海の生物がたくさんいました。黒島の歴史やウミガメのことなど、いろいろ学べます笑
黒島研究所1
黒島研究所2
黒島研究所3
黒島研究所4
黒島研究所5
そうそう、黒島には野生の孔雀がいます!なんどか見ました。
黒島に来たらここはマストかもしれません!

ICONOMA カフェ

そろそろ腹が減ったので、飯が食えるところを探しました。
カフェがあるとの噂を聞き行ってみることに
ICONOMA カフェ
ICONOMA カフェ
オーナーさんのセンスが溢れています。
お食事は、地のものを使ったトーストを食べたんですが、岩のりだったでしょうか。うますぎたので写真取るの忘れましたが、
ドリンクはしっかり取りました
ICONOMA カフェ

このあともプラプラ、ターミナルに行ったり、近くのビーチに行ったりしましたが、割愛しますw
その後、夕食の時間が迫りそうだったので宿に帰り夕食をいただきました!

夜のドライブ

夕食後、部屋でぬくぬくしていたところ、激しいノックが。宿のワイルドなおっちゃんが、「おーい、行くぞー」っと。
暇そうにしている僕に気を使ってくれたのでしょうか。どこに?と思いながら、出ていくと、「夜のドライブだ」笑

島内を車で運転して回ってくれました。牛達は寝ていました。
と、伊古桟橋へ。桟橋の上を車で突っ込みます。なんともワイルドギリギリだから落ちたらどーすんだろw


先端まで行くと、釣りをしている青年がいました!イカを釣っているそうです。


おっちゃんいわく、夜雲がなければ、星がキレイに見えて、海にその星の明かりが反射しとてもきれいだそうですが、
この日は雲が多く星があまり見えず
夜の空
向こうに見える明かりは、石垣島だそうです!石垣都会だな

桟橋の先端から、車でバックw 軽一台分ギリギリのスペースでUターンして桟橋を引き返し宿へ戻り。
おっちゃんと、もうひとりのおっさんと、請福のパックをストレートで飲みながら、おっさんゆんたくを楽しみましたw


民宿あ〜ちゃんのご夫婦にターミナルまで送ってもらい、別れを惜しみながらもお別れ。お世話になりました。また来ます。
とっても優しいご夫婦でした。

黒島に行った感想

普通に楽しかった。
島内は本当に牛ばっかりで、人の方が珍しいくらいでしたが、こんな環境なかなか味わえませんというか、多分黒島でしか味わえないと思います。
これから牛に関わる仕事(黒毛和牛牝専門の焼肉店)をする上で、牛の聖地に行けて良かったんだと思います。

島内でつぎによく見かけたのは、チャリンコで観光するおっさんですw 女子よりもおっさんのほうがよく見かけた気がします(たまたまかな)。
何人かとお話したりして、旅の醍醐味も味わえました、激しいゆんたくはないものの、2日目に聞いた沖縄のことや黒島、石垣、宮古のいろんなことが聞けて沖縄好きな僕としては、すごくいい経験となりました。


本当に牛ばかりで何もないと行ったら失礼かもしれませんが、何もない、何もしないっていうことが、本当の贅沢なのでは?っと感じることのできた旅となりました


黒島 旅 記録 - その1

黒島

八重山諸島 黒島へ行った記録です。

黒島は、石垣島よりフェリーで約30分程度の牛ばかりいる小さな島で、半日あれば島を自転車で一周できちゃいます。


牛は1歳になる前に、毎年2月に行われる競りで、日本全国各地の畜産業者に買われ、育てられ、ブランド牛となります。
僕は、黒毛和牛牝専門の焼肉屋を始めることから(現在始めている)、勉強のため各地のブランド牛となる子牛を、ここ黒島に行ってみることにしました。
黒島についての詳細は、黒島ねっとを見てください。



アクセス

新石垣空港から高速バスで約40分で石垣離島ターミナルへ、そこからフェリーで約30分程度です。
フェリー乗り場には具志堅先生がいます
具志堅

そんなに揺れることなくあっと言う間に到着
フェリーから

3時ごろ石垣を出発して夕方前に黒島に到着です。


到着すると予約していた民宿のおばちゃんが迎えに来てくれました。
車中から見える景色は草原に牛だらけ。なんとものどか。
牛だらけ
黒島は、牛の数が人の数の10倍以上で、牛10頭に対して人ひとり以下ということのようです。

泊まるところ

とりあえず宿に到着
あーちゃん家
今回お世話になったのは、民宿あーちゃん
僕の経営する伊豆下田のゲストハウスは、うーちゃん家 勝手に信憑性が湧いてここにしました。笑
おばちゃんとおっちゃんはなんともワイルドでとっても優しいご夫婦でした。またリピしたいですね。
部屋はなんパターンかあるようで、今回は個室を使わせてもらいました。

黒島の宿は「ゲストハウス」ではなく、「民宿」が一般的なようですが、ゆんたくスタイルは同じようです。


外には共有スペースがあり、ここで夜ゆんたくする方はするのでしょうか。
外の共有スペース
トイレ等は2部屋に1つあるんですかね?全く不自由しないです。

自転車で散策

とりあえず部屋にいてもただただ時間が経っていきそうなので、民宿でチャリを借りて散策に
黒島の民宿はぼぼ自転車のレンタルが無料のようです。黒島はほぼ平坦なので、快適に自転車で移動できました。
とりあえず、
適当に走る
道
すぐ牛
牛
なかなか愛嬌がある
牛
近くによったら、向こうから近くに寄ってきてくれました。牛にも性格があるんですねやっぱり。
母牛なのか?子牛なのかもはやわからず、、、たまに見せるベロを見ると牛タンって感じでした。
こんなに可愛らしく愛嬌がある生き物を僕ら人間は頂いている!っということを改めて実感し、食べ物は無駄にせず感謝して頂こうと再認識しました。笑


しばらくするとなんの店かわからないけど、看板がありました。
沖縄はSummerとWinterの切り替えタイミングはいつなのでしょうか?
看板
が、やっている雰囲気はなかったのでスルー
更に走ります。
ほんと牛ばっか
のどかな風景
これ以上遠く行くと夕食の時間に遅れて、ワイルドなおっちゃんに叱られそうなので、続きは明日にして、戻ることにしました。
帰ってきて気が付きましたが、民宿あーちゃんの近く後ろはすぐビーチがあるようだったので行ってみます。
途中にプズマリっていうなんかがありました。なんかすごい
なんか見どころ
抜けると美しいビーチが。
ビーチ
ビーチ
雨ではないけど、晴れているわけでもなかったため、写真がイマイチ。
晴れてたらもっときれいなんだろう。

ゆんたく

さ、宿に戻って一休みして、夕食です。写真撮り忘れた。
魚や肉や結構ボリューミーなので、お腹いっぱいになりました。女子には多いかもしれません。この日は、那覇からの業者のおにーさん達と、おじさん一人でした。
ご飯を食べ食堂で少しおじさんとお話をしました。よく沖縄に来るらしく、特に宮古と石垣のことは詳しくいろいろと楽しい話を聞けました。


楽しいゆんたくを期待してましたが、この日は僕も含めておっさんしかいなかったので、ときに飲むこともなく、やることもなく、携帯をいじろうと思いましたが、Softbankは電波が入らず、デザリングもできず、諦めてさっさと8時就寝笑 ちーん


ここにいたら超規則正しい生活がおくれそう。笑


その2に続く…….

黒島 旅 記録 - その2


求人! 移住・Uターン 脱サラしたい方 大歓迎



こんにちは。伊豆・下田市 白浜で、「特選黒毛和牛焼肉U」「白浜ゲストハウス うーちゃん家」を運営する 株式会社UKAI代表の鵜飼です。



上質なA4~A5 雌黒毛和牛

弊社では、焼肉店・ゲストハウスのお仕事を一緒にやってくれる方を募集しております!
あと、IT系のフリーランスの方なんかも大歓迎です。自分の仕事の片手間にやってくれる方でも全然OKです。


こんな方

  • 将来自分も経営者になりたい
  • 海が好き・海の近くで働きたい
  • Uターンして働きたい
  • 下田が好き・下田白浜を盛り上げたい

など、私達と同じ思いが少しでもある方、大歓迎です。



自分も移住してました

本当は都会を捨てて、田舎で移住したいと思っている方、多いと思います。


でもなかなか実現できないと考えてる方が多いと思います。
僕自身も5年前に脱サラしてここに引っ越してきました。




その時思ったとことは、

  • 田舎で仕事はあるか
  • 退屈しないか
  • ちゃんと生活していけるか
  • 世間体的にどうか

などなど色々考えましたが、早かれ遅かれ田舎に行きたいと思っていたので、勢い(半分ノリで)移住を決意しました。



実際移住してからは、ゲストハウスを開業し、慣れないDIYをして怪我をしたり、残金がなくなり、もやし生活になったり、色々と苦労しましたが、今となっては笑い話です。

移住を決意するための要素

ここ下田は、東京まで、約3時間ちょっと。
都会に出たくなったら休みに全然行ける距離にあり、また今後、新東名・伊豆縦貫道の延長で、都心へのアクセスがより便利になります。

それに近所にも、車を少し走らせれば大きいスーパーもあるし、コンビニは徒歩圏内だし、何不自由なく暮らせてます。

それに海・自然という魅力があります。

ただ、実際移住するとなれば、

  • 仕事
  • 住むところ
  • 移住するほどの魅力

この3つの要素で移住するかどうかを大きく左右すると考えてます。

仕事

ウチでとりあえず働いて下さい笑 また他でも、サービス業が多いかもしれませんが、たくさんあります。特に現状夏季は、どこも人手不足です。また、フリーランスの方なら、ネットワーク環境とPCさえあれば関係ないですね(僕も当初はそんな感じでした)

移住するほどの魅力

ご存知の通り、有数の観光地ですので、それだけの魅力はありますね!海・自然・ご飯

住むところ

僕が思うところ、現状ここが弱いと感じてます。特に白浜には賃貸が本当に少ないと思います。僕はここをどうにか解消するべく、色々と検討中です。

最後に

僕たちはここ白浜、下田をもっと素晴らしい「最高のリゾート地」にしたいと考えています。そのためには、移住してきてくれる方々の力が絶対に必要と僕は感じています。

もし少しでも興味を持ってくれた方がいらっしゃいましたら、お気軽にご相談、ご連絡をいただけると嬉しいです。

よろしくお願い致します。

株式会社UKAI 代表 鵜飼
メール:info@ukai-mnap.co.jp


サーバレス AWS s3 + cloudfront + route53 + ACM で静的website ホスティング のメモ

今更ですが、サーバーを使用せず、node環境でコンパイルした静的ファイルを、AWS上で公開する方法のメモです。
静的コンテンツの配信性能を高めるために、cloudfront経由でs3にアクセスする正しい方法と、route53でドメイン管理、ACMでhttps対応をするなど、サーバーレスでのAWS上のwebサイトの公開方法をメモしておきます。

事前準備

  • AWSアカウント
  • 公開するファイル(html,js,cssなど)を用意
  • 公開用のドメインを取得済み

S3にバケット作成

  • Consoleより S3へ
  • s3画面より、バケットを作成し、バケット名を登録
  • あとは、基本そのままで「次へ」
  • パブリックの設定にはしないこと(特定のCloudfront distribution経由のみからアクセスさせる)

Cloudfrontの設定

  • Consoleより Cloudfrontへ
  • Cloudfront画面より、「Create Distribution」
  • Webとして、Get Start

cf1

  • Restruct Bucket をYESにし、このRestributionからのみアクセスできるようにidentifyを作成し、s3のCROSの設定を反映させる

cf2

  • 公開するドメインをAlternate Domain Namesに指定
  • SSL Certificateは、ACMにてcertificateを作成して指定する
  • ACM作成時に、ドメインの所有権を確認するために、通常Emailでの認証を選択しますが、下記のアドレス宛に確認メールが飛ぶので、いずれかのアドレスで受信できるよに事前に設定しておく必要がある
administrator@your_domain_name
hostmaster@your_domain_name
postmaster@your_domain_name
webmaster@your_domain_name
admin@your_domain_name

詳細:https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/gs-acm-validate-email.html

cf3

  • Default Root Objectに index.html等入れておく
    ここを設定しないと、https:xxxxxxx.com/ と言う形でファイル名を省略してアクセスした際に、cloudfrontが判断できなくなる
  • これで 「Create Distribution」

※ 一覧から「Enable」になるまで少し時間がかかります。尚、「Enable」になっても、Cloudfrontの「Domain Name」にアクセスしても、URL上S3のURLにリダイレクトされることがありますが、時間が経てば解決しますので、次の設定をして、気長に待ちましょう。

Route53の設定

  • Consoleより Route53へ
  • Hosted Zoneが未登録(新たにドメインを登録する)場合は、「Create Hosted Zone」から「Domain Name」を入力して、作成する
  • 該当Domainを選択し「Create Record Set」

Route53


S3へファイルをUP

まとめ

  • 設定が反映されるまでにしばらく時間がかかりますので、気長に待ちましょう。その後、登録したドメインにアクセスしてみます。
  • httpでアクセスした際、httpsにリダイレクトするかどうかも確認します。
  • ClourdFrontのcacheはデフォルトで24時間なっていますので、動的部分が多いコンテンツの場合は、設定の変更もしくは、Create Invalidationにてcacheをクリアする必要があります。
  • cloudfrontの反映に時間がかかるので、気長に待ちましょう。思った通りの動作にならず、設定を間違えているのかと思いがちですが、大丈夫です。

参考

CloudFront + S3 特定バケットに特定ディストリビューションのみからアクセスできるよう設定する
S3の特定バケットへのアクセスを特定のCloudFrontからのみ許可する
CloudFrontでS3のウェブサイトをSSL化する


AWS LamdbaからElasticSearchへアクセスする設定方法(それぞれVPC内に配置)

Lamdba関数を使って外のAPIを叩き、そのデータをElasticsearchへ流し込むという処理を作ろうと検討していたところ、
lambda関数からElasticsearchへ何か操作をする際、どのように接続しセキュリティを保つのか、考えものです。
色々な記事を見ていると、Lamdbaも、Elasticsearchも、VPC内への作成が可能になっているようです。
これを使い下記のような構成を取るように対応しました。
構成

が、Natゲートウェイを使用するので(vpc内のlamdbaから外部へアクセスするのに必要)、安くても約30ドル程かかります。
だったら、microインスタンスを上げたほうが良さそうですので、今回は、両方ともvpcから外し、iam認証によりlambdaからelasticsearchにアクセスしようと思います。
構成



ちなみにlamdba処理のコールは、cloudwatchを使って定期実行するものです。
https://qiita.com/RyujiKawazoe/items/64e8a65ab3e4909678fc

データ取得時は下記のような構成です。
構成

取得用は、cognitoかなんかで認証するか(VPC外の時)、パブリックサブネットに配置するなどして(VPC内の時)直接elasticsearchを叩けばいいかと思いましたが、せっかくなので、作成時と同様にlamdbaでelasticsearchへアクセスし取得しようと思います(lamdba呼び出しはAPI gatewayから)。

前提

Lamdba用のロールを作成

Lambdaから必要なアクセスは、デフォルトのlog書き込み等、Elasticsearch、VPC内にLamdbaを配置するためのポリシーが必要になります。

  • AWSLambdaBasicExecutionRole(lamdba作成時のdefaultのやつ)
  • AWSLambdaElasticsearchExecutionRole(Elasticsearchへのアクセス)
  • AWSLambdaVPCAccessExecutionRole(VPCへのアクセス)
    ※これは、VPC内に配置する際に使用するので、外に出す場合はもちろん必要ありません。


    これらのポリシーを持つロールを作成します。
  1. ロール作成
  2. 「使用するサービスを選択」で、Lamdbaを選択
  3. 「アクセス権限ポリシーをアタッチする」で、上記3つ(2つ)を検索し、チェックを入れる
  4. 次のページでロール名を入力して作成する
  5. 作成表示されるARNを控えておく(Elasticsearch側にこれを設定するため)
    iam role
    ※ここで、AWSLambdaVPCAccessExecutionRoleを追加しないと、VPC内にLamdbaを設定しようとする際に、怒られます。
    https://qiita.com/yoshidasts/items/a369f89d34f57ea67aad


Lamdba関数をVPC内に設定

lamdbaをVPC内に置かない場合は、ここは割愛する

ロールの設定

新規作成の場合は作成時のロール選択で、上記で作成したロールを指定します。既存の関数がある場合はそれを選択し、関数詳細画面で設定します。
lamdba role

VPC

VPC内にLambdaを配置すると、VPC内のサービスへの接続はとても簡単ですが、インターネットに疎通させようとした時に、外に出られないので、ちょっと設定が必要です。
VPC、サブネット、Natの割当
https://qiita.com/kojiisd/items/a15c1807c5c6bb08add0
こちらを参考

サブネットはプライベートサブネットを指定する
lamdba vpc

Elasticsearchを起動

※ ここはVPC内に配置しない場合は、パブリック・アクセスにし、アクセスポリシーを設定します。VPC内に配置する際は、同一VPC内からのみアクセスできなくなるので、アクセスポリシーは特に設定しなくてもいいかと思います。

手順に沿って設定していきますが、VPCのところで、Lamdbaに設定したVPCやサブネット、セキュリティグループを指定します。
es vpc

アクセスポリシーを「1つ以上のAWSアカウントまたはIAMユーザーにアクセス許可….」を選択し、
es policy

ARNに先程作成したロールのARNを指定する
es arn

VPC内か外かで、上記どちらかを設定

これで起動します。

※Elasticsearchの起動時の設定詳細はこちらを参考に
https://dev.classmethod.jp/cloud/aws/cm-advent-calendar-2015-getting-started-again-amazon-es/


まとめ

本来は同一vpc内に配置すべきかとは思いますが、ラーニングコスト面等を考え、外に出し、iam認証の設定をしました。
lamdba関数で、elasticsearchへの接続し、index作成とデータの取得については、別の記事で記載しようと思います。


Percel で fsモジュールが動かない

Webpackの代わりとなるかと、注目されているpercelですが、まだちょこちょこ厳しいところがあるようです。
es6を使ってますが、babelにてprecompileする際に、問題がありました。
ファイルの操作を行うnode module fs moduleが動かず、とても困りました。

その際の対応方法をメモっておこうと思います。

すでに治ってるかもしれません。(2018-03-03現在)

fs module

これは下記のように、node 環境にてファイルの読み込みや有無、dir以下のファイルを取得する際に使うmoduleですが、percelにてprecompileする際、このfs noduleが正常に認識されず、そのまま出力されてしまうため、ブラウザで認識できない or、percelコマンドでコンパイルする際に下記のようなエラーとなりました。

Oh no, inlining via fs.readFileSync is not working: TypeError: _fs2.default.readFileSync is not a function. (In '_fs2.default.readFileSync(__dirname + '/name.txt', 'utf8')', '_fs2.default.readFileSync' is undefined)

Github上にも結構issue登録されてます。
https://github.com/parcel-bundler/parcel/issues/106

対応方法

対応方法はいくつかあったのですが、僕は下記の方法で解決できました。
parcel-babel-inline-test

  1. Stop parcel
  2. Update .babelrc
{
  "presets": [
    ["env", {
      "modules": false
    }]
  ]
}
  1. Clear the parcel cache (rm -rf .cache)
  2. percel コマンドで開始
  3. Open http://localhost:1234

まとめ

細かいことまでは僕は調べなかったのですが(そこはすごい人達に任せます)、これでとりあえず使えるようになりました。


Instagram API を AWS API Gateway でWrappingして、node環境からCognitoを使って叩く方法

S3、Cloudfrontにてwebサイトを公開してますが、jsからSNS(ここではInstagram)のAPIを使用する際、気になるのがAccess token。
Access token をパラメータに含みたくないので、AWSのAPI gatewayでラッピングして、使用するようにしました。
更にそのAPIをAWS_IAM認証制限し、Cognitoを使って node 環境から叩く方法についてメモしておこうと思います。

各種必要なAPIの準備

僕は今回InstagramのAPIを叩きたかったので、下記を参考にdeveloper登録をして、access tokenを取得してAPIを叩けるようにしました。
Instagram APIを使ってWebページに表示する
Instagram Developer

API Gatewayの設定

処理の流れとしては下記です。

  1. API Gateway を叩く
  2. API GatewayからLambda関数が呼ばれる
  3. Labda関数が、必要なパラメータ等の処理をして、Instagram APIを叩く
  4. レスポンス返却

それではAPI Gatewayを設定していきます。

AWSコンソールにログイン -> API Gatewayへ -> APIの作成をクリック

API作成

リソースの作成

API自体が作成されたので、次にリソースを作成します。
アクション -> リソースを作成 を選択します。
API作成3

  • リソース名
  • API Gateway CORSを有効にするにチェック

メソッドの作成

作成されたリソースを選択した状態で、アクション -> メソッドの作成を選択します。
API作成4
セレクトボックからGETを選択し、チェックマークをクリック

ただAPIを叩くラッピングするだけなら、ここで統合タイプをHTTPを選択し、エンドポイントにそのAPIを指定すればOKですが、ただ、client から渡ってきた Query String を endpoint に渡す 際、key は変更できるものの、value を変えたり、特定の key を静的に(=固定で)指定といったことはできないようですので、今回はLambda関数を使用します。
API作成5
今回はAccess tokenを静的に渡したいので、lambda関数を使用します
API Gateway + Lambda で固定のパラメータを付加して HTTP Proxy する

クエリパラメータの設定

今回はinstagram api で投稿データを数軒取得しましたが、全件取得しても無駄なので、必要な件数のみ取得しようと思います。Instagra api 側で countというクエリパラメータがあるので、これをAPI gatewayで設定したAPIにも付加できるように設定します。
API作成6
GETメソッドを選択状態にし、「メソッドリクエストから」クエリ文字列の追加から、クエリ名を入力します。


次に「統合リクエスト」から一番下本文マッピングテンプレートから、追加し、content-typeを「application/json」に設定し下記のように設定します。
API作成65
※、ダブルクォートで囲わないとうまく行きません。

ここまでで、一旦API Gatewayの設定は終わりです。後に、deployとアクセスの制限の設定をします。
次にLambda関数を準備します。

Lambda関数の設定

関数の作成

AWSコンソールより、Lambdaを選択し、関数の作成をクリック
Lambda

トリガーの設定

トリガーを追加します。
Lambda2

関数のコード作成

今回はnode.jsを使います。
local環境にて、

$ yarn init

もしくは

$ npm init

し、projectを作成します。

必要なパッケージを入れます。

$ yarn add requrest 

もしくは

$ npm install request --save

jsファイルを作成します。


index.js

var request = require('request');
exports.handler = function(event, context) {
    var params = { count:event.count, access_token: process.env.ACCESS_TOKEN}; 

    request.get({url:process.env.URL, qs: params}, function(err, res, body) {
      if (!err && res.statusCode == 200) {
        context.done(null, body);
      } else {
        console.log('error: '+ res.statusCode);
      }
    });
};
  • process.envは、後ほどLambda 側で設定します。
  • 単純に、requestを使って、APIを叩く処理です。
  • event.countは先程API Gatewayで設定した、クエリパラメータになります。

次にこれら(index.jsと作成されたnode_modulesの2つ)をLambdaにアップロードするために、zipで圧縮します。

アップロードします。
Lambda3

環境変数を設定

コード中のprocess.envの値を設定します。
Lambda4

これでLambdaの設定は以上になります。

API Gatewayをdeployする

デプロイ

AWSコンソールにログイン -> 再びAPI Gatewayへ -> APIの作成をクリック
先程作成した、APIを選択します。

API ルートを選択し、アクションから、APIのデプロイを選択
API作成7

エンドポイントの確認

左メニューのステージを選択する
API作成8

ここまでで、APIのラッピング作業は終わりです。実際に表示されているURLを叩いて結果が帰ってくればOKです。
設定した、クエリパラメータ(count)も指定してみましょう。

これでOKですが、APIのURLがバレたら誰でも叩けてしまうのはちょっと嫌なので(jsから使うため)、Cognitoを使って制限します。

API Gatewayに認証をつける

AWSコンソールにログイン -> 再びAPI Gatewayへ -> APIの作成をクリック
先程作成した、APIを選択します。
API作成9
GET を選択し、認証をAWS_IAMに設定します。

これで先程と同じようにデプロイし、エンドポイントに表示されているURLをたていてもデータを取得できないことを確認します。

ARNのメモ

API Gatewayの画面で、ARNをメモしておきます。
API作成10


Cognitoの設定

プールIDの作成

AWSコンソールにログイン -> Cognitoへ -> フェデレーテッドアイデンティティの管理を選択 -> 新しい ID プールの作成
Cognito
プールIDを作成します


ポリシーの設定

Cognito2
ポリシードキュメントを下記のように設定します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "execute-api:Invoke"          
      ],
      "Resource": [
        "arn:aws:execute-api:xx-xxxx-xx:xxxxxxxxxxxx:xxxxxxxxxx/*/GET/instagram"
      ]
    }
  ]
}
  • Resource部分は、execute-api:リージョン:arn-idになります。先程メモった内容です。

jsコードを取得

Cognito3

これでcognitoの設定はOKです。


Node 環境から実際に使ってみる

AWS API Gateway は便利なサービスですが、Node.js から使おうとすると、自動で生成される JavaScript のSDKがブラウザ用しか用意されていなかったり、エンドポイントごとにメソッドが自動で生成されていて、APIのエンドポイントを変更するごとにSDKを生成し直さなければならなかったりと不便だったため、Node.js から API Gateway を簡単に使えるパッケージを作ってくれた方がいらっしゃいましたので、こちらを使用させていただきました。ありがとうございます。
Node.js から API Gateway を簡単に使えるパッケージ作りました

準備

$ yarn add aws-sdk
$ yarn add aws-api-gateway-client

もしくは

$ npm install add aws-sdk
$ npm install aws-api-gateway-client

※parcelを使っていますが、babelモジュール等の依存があるので、このパッケージで使っているモジュールを入れます

.babelrc

{
"presets": [
  "stage-0",
  "@ava/stage-4",
  "babel-preset-es2015",
  "babel-preset-es2016",
  "babel-preset-es2017",
],
"plugins": ["transform-es5-property-mutators"]

}

上記が必要なものですので、すべてnpm or yarnで入れます。各moduleについてはググれば出てきますのでお願いします。
2018/03 これは、babelrcに入れなくても、aws-api-gateway-clientに上記が必要なだけですので、install しておくだけでOKです。

$ yarn add --dev babel-plugin-transform-es5-property-mutators @ava/babel-preset-stage-4 babel-preset-stage-0 babel-preset-es2015 babel-preset-es2016 babel-preset-es2017

OR

$ npm install --save-dev babel-plugin-transform-es5-property-mutators @ava/babel-preset-stage-4 babel-preset-stage-0 babel-preset-es2015 babel-preset-es2016 babel-preset-es2017


コード

  import AWS from 'aws-sdk'

  let apigClientFactory = require('aws-api-gateway-client').default

  // Amazon Cognito 
  AWS.config.region = 'xxxxxxxxxxx'
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
      IdentityPoolId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  })

  AWS.config.credentials.get(function(err){
    if(!err){
      const apigClient = apigClientFactory.newClient({
        accessKey: AWS.config.credentials.accessKeyId,
        secretKey: AWS.config.credentials.secretAccessKey,
        sessionToken: AWS.config.credentials.sessionToken,
        region: 'xxxxxxxx',
        invokeUrl: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
      });
      let params = {
        //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API
      };
      // Template syntax follows url-template https://www.npmjs.com/package/url-template
      let pathTemplate = 設定したAPIのパス
      let method = 'GET'
      let additionalParams = {
      //If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here
        queryParams: {
          count: 必要な件数,
        }
      };
      let body = {
        //This is where you define the body of the request
      };

      let items = []
      apigClient.invokeApi(params, pathTemplate, method, additionalParams, body)
      .then(function(result){
        // 成功処理
        });
      }).catch( function(result){
        //This is where you would put an error callback
        // エラー処理
      });
    }else{
      console.log(err)
    }
  })
})

以上です。

まとめ

これで安全にAPIを叩くことができるようになりました。
意外とnode環境からAPI gatewayを叩くすべがあまりなく、たまたま作ってくださっていた方がいらっしゃいましたので、助かりました。
ありがとうございました。


サーバーレス 問い合わせフォーム AWS s3/lambda/cognito/ses/slack

webサーバを使用せず、s3 でwebを公開してる際のお問い合わせフォームの実装方法です。

前提

  • Aws s3にてwebサイト公開済み
  • slackアカウントを持っている
  • sesにて特定のdomainにて認証済み

※参考
yarn と parcel と riot を使って簡単な webサイトを作ってみた
Webpack + Riot + Materializeで、Webサイト環境を作った話

処理の流れ

  • 問い合わせフォームからs3にjsonファイルがupされる(congnitoを使って)
  • s3にアップされると、lambda関数が処理実行
  • lambda関数がses(ユーザー用)とslack(管理用)に送信

やること

  • s3にbucket作成
  • cognito設定
  • slackの準備
  • sesの準備
  • lambda設定
  • フォーム実装

s3にbucket作成

s3にフォームからupされる専用バケットを作成します。
一応指定のサイト以外からアップされないように「CORS設定」をしておく(本番反映後設定したほうがローカルでの開発中に楽)

アクセス権限 -> CORSの設定

<!-- Sample policy -->
<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>開発中は * 公開後 公開ドメインに変えた方がいい</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

cognito設定

Cognitoについては下記を参考にすると理解できそうです。
https://dev.classmethod.jp/cloud/aws/what-is-the-cognito/

cognito IDプールを作成する

  • AWSコンソールより、Cognitoを選択し、「フェデレーテッドアイデンティティの管理」を洗濯、右上の「新しいIDプールの作成」を選択。
  • 「ID プール名」を、わかりやすい名前に
  • 「認証されていない ID に対してアクセスを有効にする」にチェックを入れる
  • 「プールを作成」する

次の画面に進むみ、詳細を表示
キャプチャ
jsコードを控えておく

ポリシーを作成し、ロールにアタッチする

作成されたcognitoのロールに対して、s3バケットへのファイルup権限を追加します。

  • AWSコンソールより、IAMを選択し、ポリシーを選択。
  • ポリシーの作成をクリックし、JSONに切り替える
  • 下記をコピペ(指定したs3のarnに対してput権限を付与する)

    {

    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::先程作成したバケット名/*",
                "arn:aws:s3:::先程作成したバケット名/*"
            ]
        }
    ]
    

    }

  • 作成をクリックし、次の画面で、ポリシー名にわかりやすい名前を入れて作成する

作成されたポリシーをロールにアタッチする

※ IDプール作成時にポリシー詳細にて設定したほうが楽かも

  • AWSコンソールより、IAMを選択し、ロールを選択。
  • 一覧より、作成された、「Cognito_xxxxxxxxxxxxxUnauth_Role」を選択。※Unauthの方
  • ポリシーのアタッチ
  • 上記作成したポリシーを検索欄より、検索し、チェックを入れる
  • ポリシーのアタッチ

※ この手順は、cognito IDプール作成じにポリシーの設定からやったほうが楽かもしれません。

一応、これでcognitoの準備はOKです。

slackの準備

とりあえずこちらの方法を参考にさせていただきました。
http://reiki4040.hatenablog.com/entry/2017/01/30/001634
webhookのURLだけ、コピーして控えておきます。

sesの準備

sesは、リージョンが限られているので、限られたリージョンの中から、選択する。

  • Aws コンソールより、sesを選択
  • 左メニューの「Email address」を選択し、通知元のEmailアドレスを指定する
  • Verify This Email Adressを選択する
  • 登録したアドレスに承認メールが届くので承認する

lambda設定

指定のS3バケットにデータがupされたら、upされたファイルの情報を元に、そのメールアドレスへ送付するし、管理側には、slackにて、通知する
最初に作成した、s3のバケットを使用します。

まずは、lambdaより、sesへのアクセスを行うのに、ロールの設定をします。

ロールを作成

  • IAMから、ポリシー、ポリシーの作成
  • Awsサービスをlambdaを選択し、次のステップ
  • ポリシータイプで、sesと検索し、「AmazonSESFullAccess」にチェックを入れて次のステップ
  • また、cloudwatchへの権限も追加するので「AWSOpsWorksCloudWatchLogs」と検索し、チェック
  • ロール名にわかりやすい名前を入れる

lambda関数を作成

Awsコンソールより、Lambdaを選択し、関数の作成をクリック

  • 名前 -> わかりやすい名前
  • ランタイム -> node6.10
  • ロール -> 既存のロールを選択
  • 既存のロール -> 先程作成したsesとcloudwatchのポリシーを持つロールを指定

これで関数の作成

  • 環境変数に「SLACK_WEBHOOK_URL」のkey名で、先程のslackのwebhookのURLを指定する
'use strict'
const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });

console.log('Loading function');

// Set for slack.
const https = require('https');
const url = require('url');
const slack_url = process.env.SLACK_WEBHOOK_URL;
const slack_req_opts = url.parse(slack_url);
slack_req_opts.method = 'POST';
slack_req_opts.headers = {'Content-Type': 'application/json'};

exports.handler = (event, context, callback) => {
    //console.log('Received event:', JSON.stringify(event, null, 2));

    // Get the object from the event and show its content type
    const bucket = event.Records[0].s3.bucket.name;
    const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
    const params = {
        Bucket: bucket,
        Key: key,
    };
    s3.getObject(params, (err, data) => {
        if (err) {
            console.log(err);
            const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
            console.log(message);
            callback(message);
        } else {
            const message = JSON.parse(data.Body);
            const body = [
                '[お名前] ' + message.name,
                '[メールアドレス] ' + message.email,
                '[日時] ' + message.date,
                '[お問い合わせ内容]' + "\n" + message.body,
            ].join("\n");

            // slack
            const req = https.request(slack_req_opts, function (res) {
                if (res.statusCode === 200) {
                    context.succeed('posted to slack');
                } else {
                    context.fail('status code: ' + res.statusCode);
                }
            });

            req.on('error', function(e) {
                console.log('problem with request: ' + e.message);
                context.fail(e.message);
            });

            req.write(JSON.stringify({text: "お問い合わせがありました\n\n" + body}));
            req.end();

            // ses
            const email = {
                Source: "xxxxxxxxxxxxxxxxxx",
                Destination: { ToAddresses: [message.email] },
                Message: {
                    Body: { Text: { Data: message.name + "様\n\nテスト送信\n\n" + body} },
                    Subject: { Data: "お問い合わせありがとうございます" },
                },
            };
            const ses = new aws.SES({ region: 'us-east-1' });
            ses.sendEmail(email, callback);
        }
    });
};

トリガーの設定

左側のリストからトリガーを追加します。

  • s3を選択
  • ページ下記にて、バケット名の指定
  • ObjectCreatedByPut を指定する

フォーム実装

例では、roit.jsを使っていますが、必要に応じて変更して下さい。

$ yarn add aws-sdk

コード

// Amazon Cognito 認証情報プロバイダーを初期化します
AWS.config.region = 'us-west-2';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: 'xx-xxxx-xx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
});
submit (event) {
  event.preventDefault()

  let s3BucketName = "バケット名"

  let email = this.refs.email.value
  let name = this.refs.name.value
  let body = this.refs.message.value
  let submit = this.refs.submit
  submit.disabled = true

  let now = new Date();
  let obj = {"name":name, "email":email , "body":body, "date": now.toLocaleString()}
  let s3 = new AWS.S3({params: {Bucket: s3BucketName}});
  let blob = new Blob([JSON.stringify(obj, null, 2)], {type:'text/plain'});
  s3.putObject({Key: "uploads/" +now.getTime()+".txt", ContentType: "text/plain", Body: blob, ACL: "public-read"},
    function(err, data){
      if(data !== null){
          成功処理
      }
      else{
          失敗処理
      }
    }
  );

webに公開後

s3のバケットのCORSの設定で、ドメインを指定して、指定ドメイン以外からfileをアップできないようにしておきましょう。

まとめ

cognito のIDプールを使用して、s3へファイルアップ、lambda関数より検知して、sesとslackにメッセージを送信することができました。
ec2等webサーバーを使っていればサーバ側で処理をして実装可能ですが、サーバーレスにして、この方法でも簡単に実装でき、トータル的な料金も、全然安いです。
ちょっとしたwebサイトなら本当にwebサーバーは不要になってきましたね。

ご意見ご指摘があったらお願いします。