ぺい

渋谷系アドテクエンジニアの落書き

IntelliJ IDEA プロジェクト内移動を使いこなす

f:id:tikasan0804:20180729133436p:plain

案外ちゃんと使いこなせていないソースの行き来機能

IntelliJ IDEAと言いつつ、スクリーンショットはGolandでやっています。

わりと規模大きめの開発が多くなってきて、様々なファイルを行き来することが増えてきて、結構ファイルを探すという無駄な時間が発生していることに気づいたので、今回は費用対効果高そうだったので、プロジェクト内移動のNavigation機能についてまとめたいと思う。

この記事に書いていることは以下の本に大体書いています。

ショートカットも基本的にわかる範囲で紹介します。(ideaVimを普段使っているのでわかる範囲で書く) また、私は相変わらずショートカットの記号が覚えられないので、ここではAltやCtrlと書きます。

macOS Sierra: メニューに表示される記号

コードジャンプ系

シンボル定義箇所へジャンプ

定義されている変数や関数の定義元にジャンプする。

  • ideaVim Ctrl + ]
  • Mac Command + B
  • Win Ctrl + B

一箇所しかない場合は、一発でジャンプしますが、複数定義箇所がある場合は、ポップアップが出てきます。

f:id:tikasan0804:20180729121021p:plain

シンボルの利用箇所の一覧

  • Mac Option + F7
  • Win Alt + F7

何かのシンボルにカーソルを合わせて、使うと、その定義箇所から利用箇所が一覧で見れる。

f:id:tikasan0804:20180729120802p:plain

ジャンプを戻る

ジャンプ先から戻る

  • ideaVim Ctrl + t
  • Mac Command + [
  • Win Ctrl + Alt

ジャンプ先に戻る

  • Mac Command + ]
  • Win Ctrl + Alt +

クラス間の移動

子クラスから親クラスへ移動。(Javaとかの継承)

  • ideaVim クラス名に合わせて、シンボルへのジャンプで同じことができる
  • Mac Command + U
  • Win Ctrl + U

親クラスから、子クラスや実装クラスを見ることもできる

  • ideaVim クラス名に合わせて、シンボルへのジャンプで同じことができる
  • Mac Option + Command + B
  • Win Ctrl + Alt + B

ファイル系

直近のファイルを開く

Ctrlを押したまま、Tabを押した回数だけ前のファイルに戻れます。

  • Mac,Win Ctrl + Tab

f:id:tikasan0804:20180729123422p:plain

直近のファイル一覧

上のTab を何度も押す形式は正直だるいので、開きっぱなしにできるこっちのがおすすめ。

  • Win Command + E
  • Mac Ctrl + E

f:id:tikasan0804:20180729123752p:plain

ディレクトリ系

ナビゲーションバーを使って移動

ナビゲーションバーを開いて、矢印キーEnterとかをつかうと、ソースを開く事もできます。

  • Mac Command + 1
  • Win Alt + 1

f:id:tikasan0804:20180729125325p:plain

ナビゲーションバーの操作型

新規ファイル作成

  • Mac Command + N
  • Win Alt + Insert

ファイル名変更

  • Mac,Win Shift + F6

現在開いているファイルにフォーカスを当てる。

  • Mac Command +
  • Win Alt + Home

現在開いているファイルをナビゲーションバーと同期させる

いま開いているファイルってソースどこ?っていうのが案外わかりにくかったりするので、自分は同期させたりしています。
※上に出てる階層表示でわかるっちゃわかりますが

f:id:tikasan0804:20180729130537p:plain
f:id:tikasan0804:20180729130541p:plain

検索

Search Everywhere

色んな検索をサクッとできる。

  • Mac,Win Shift 2回押す

f:id:tikasan0804:20180729131305p:plain

種別付き検索

ファイル名検索

  • Mac Command + Shift + O
  • Win Ctrl + Shift + N

クラス名検索

  • Mac Command + O
  • Win Ctrl + N

シンボル名検索

  • Mac Option + Command + O
  • Win Ctrl + Shift + Alt + N

シンボル・検索除外

Excludedに指定すると検索対象として出なくなる。
よくあるケースとしては、jsのdistフォルダ出てくるなあああああ!とか。

f:id:tikasan0804:20180729131715p:plain

TODO一覧

FIXME TODO をコード上で書くと、一覧して見ることが出来る。

  • Mac Command + 6
  • Win わからなかった

f:id:tikasan0804:20180729132637p:plain

チャットと対話の使い分け

コミュニケーションは難しい

社会人になって、早いもので数ヶ月が経ちました。
いま、自分は広告配信システムの開発(株式会社Zucks)をしているのですが、学生から社会人になってから、最も難しいと感じ、日々課題を感じているのがコミュニケーションです。苦労しながらも、最近わかってきたことの一つであるチャットと対話の使い分けどうすれば良いんだっけってところをまとめてみる。
※弊社では営業も開発も、Slackをチャットツールとして使っています

チャットと対話の質的な違い

チャット

  • 証拠能力が高い
  • 時間のある時に返せる
  • 他の人が見える
  • 書いたこと以上のことは伝わらない(前後関係とかで分かることもある)
  • ひとまず雑に投げれる

対話

  • 温度感が伝わる
  • 深い議論が出来る
  • 時間が取られる
  • 体力使う

上に書いたこと以外にも色々あるとは思いますが、雑に思いついたものを書きました。

それぞれの立ち位置

どちらもした方が良いことには変わりはないのですが、あえて言うなら、チャットは マスト 対話はベター という感じで、この差を生んでいるのは、証拠能力です。
対話は話をしている人以外には議論が見えません。じゃあ、対話は駄目なのか!?っていう話ではなく、対話をしたことをざっくりで良いのでチャットとかに残す。または結構な分量になるなら、Googleドキュメントとかに議事録残す。

「という話をした」はチャットで共有する

弊グループ全体がどうかは知りませんが、少なくともZucksでは、「という話をした」というワードがSlackにかなり出てくる。これは、誰とどういう話をしたとか、対話で決まったこととかをSlackに流す習慣がある。これはかなり良いことで、議論に参加していない人でも、ひとまずチームがどういう動きをしているかを知ることが出来る。知りたい情報なら、チャットとかで聞けば良いということが出来る。これがどこにも残っていないと、それすら出来ないので、この習慣は非常にありがたい。(これは別に誰にも指示されていない)

slack上で「という話をしたで2000件も出てきた」 f:id:tikasan0804:20180717213108p:plain

中にはこんな雑な「という話をした」 f:id:tikasan0804:20180717215011p:plain

チャットを投げる時はどういう時

自分の中で、2つの条件が決まっていて、以下の2つです。

  • 何を質問したら良いか分からない
  • 質問を完全に言語化出来る自信がある

何を質問したら良いか分からないのにチャット!???

チャットなのに、そんな雑で良いのか??!!!って感じかもしれませんが、まず前提として、このフェーズの時は、誰かにメンションをつけて投げるのではなく、雑にチャットに書く。作業をしている中でのモヤモヤとかはissue経由で全部チャットに流れるので、GitHub経由で流すこともかなり多い。
これは、いまここらへんで悩んでいるけど、誰に質問したらいいかも分からないし、質問するべき項目も分からない。そんな時に、チーム内の人に相談をしても、時間ばっかり食ってしまうので、こういうフェーズの時はチャットに書くようにしている。分かる人がいたら、返してくれることもあれば、自己解決することもよくある。

訳が分かっていないことしか分かっていないことをボヤく
f:id:tikasan0804:20180717215023p:plain

強いおじさんが助けてくれることがよくある
f:id:tikasan0804:20180717215841p:plain
f:id:tikasan0804:20180717215851p:plain

質問を完全に言語化出来る自信がある

さっきとは違って、完全に理解しているフェーズの時は、チャットで投げることが多い。というのも、言語化が出来ているレベルになっている時は、ばちっと!っとチャットで投げる。このフェーズに入っている時は、短いチャットのやり取りで終わる。しかし、思ったよりちゃんと話した方がいいかも?ってなった時は、対話をして解決するということもある。そして、話が終わったら、「という話をした」で質問内容の答えをチャットで共有する。

チャットで無理な時は丸テーブルで話そう「マルテ」が発動する f:id:tikasan0804:20180717221557p:plain

対話をする時

対話をする時もチャットと同様で条件が2つあって

  • チャットだけだと長くなりそう
  • ペアプロ、ペア作業、ペアhoge

チャットだけだと長くなりそう

これは、質問を完全に言語化出来る自信がある に書いたように、話した方が良さそうだ!って思った時に対話する。

ペアプロ、ペア作業、ペアhoge

ペアで一緒に進めた方が早そう。ということもあって、弊社では結構ペアプロをすることがあります。その理由としては、プロダクトコードの構成とかを理解するのに時間がかかりそうとか、言語ごとにあるパラダイムとかをサクッと理解しようみたいな時、あとは削除作業のような怖い作業はペアで確認しながらやったり、あとは何で落ちているか本当に分からないコードとかを一緒に読むこともあったりします。

etc...

  • issueに自分の意思決定は全て書く
  • GUI操作はスクリーンショットを貼る
  • 質問は短くわかりやすくすることを頑張る
  • 事実と事実からの推測かを明確にする
  • 数字が出せるなら、多いとか少ないではなく数字を書く

なんか、他にも色々ある気がする。

今回書いたこと

チームとして「こうしなさい!」ということは一切言われていません。うちのチーム的にこうやって動いた方がいい感じになりそうと思って、日々やってることをまとめたものになります。書いてて思ったのが、こういう文化ってどうやって作ってるんだろうなと思いました。

シェル、シェルスクリプトの使い方まとめ

f:id:tikasan0804:20180710081015p:plain

自分向けメモ。たまに更新していく。

Shellとは

ユーザーが入力したコマンドを解釈し、該当コマンドを実行する。シェルにはモードが2つある。

上記がどういったものかは割愛する。

単純コマンド

$ echo HelloWorld
HelloWorld

コマンド名と任意の引数で構成されるもの。

リダイレクト

上記をリダイレクトさせることが出来るのが、リダイレクトです。(良い言い方がわからない)

標準入力にファイルを入力する例

$ cat stdin
HelloWorld

$ < stdin
HelloWorld

標準出力をファイルに書き込む例

$ cat stdin
HelloWorld
$ cat stdin > out

$ cat out
HelloWorld

標準出力をファイルに追記する例

$ cat stdin
HelloWorld
$ cat stdin >> out2
$ cat stdin >> out2
$ cat out2
HelloWorld
HelloWorld

標準出力・標準エラーを別ファイルに書き込む

ファイル記述子

div:
        echo 1
        2
$ make div 1> log_1 2> log_2
$ cat log_1
echo 1
1
2
$ cat log_2
make: 2: No such file or directory
make: *** [div] Error 1

標準出力・エラーまとめてファイルに書き込む例

err:
        echo HelloWorld
        a
$ make err > log 2>&1
$ cat log
echo HelloWorld
HelloWorld
a
make: a: No such file or directory
make: *** [err] Error 1

パイプ

構文の組み合わせ

treeの出力をlessに渡す

$ tree / | less

標準エラー出力もパイプに渡す

hoge 2>&1 | less

環境変数の一時変更

LANG=C man ls

&&(AND) ||(OR)

パイプライン1 && パイプライン2はパイプライン1の終了ステータスが成功(0)なら、パイプライン2が実行される。
パイプライン1 || パイプライン2はパイプライン1か2のどちらかが成功(0)すればおk.

err:
        echo HelloWorld
        a
$ make err && echo OK
echo HelloWorld
HelloWorld
a
make: a: No such file or directory
make: *** [err] Error 1

$ make err || echo OK
echo HelloWorld
HelloWorld
a
make: a: No such file or directory
make: *** [err] Error 1
OK

シェルスクリプト

行頭便利フォーマット

#!/usr/bin/env bash

# Fail on unset variables and command errors
set -ue -o pipefail

# Prevent commands misbehaving due to locale differences
export LC_ALL=C
#!/bin/sh -e
# オプションつきシェルスクリプト

行頭の意味

シェルスクリプトの行頭に記述する #! で始まる行shebangという

bash絶対パス指定

#!/bin/bash

bashをenvを使って指定

#!/usr/bin/env bash

基本構文

if

引数によっての分岐

#!/bin/bash

if [ $#  -lt 2 ];then # 引数が2個未満
  echo "Usage: $0 file1 file2" 1>&2 # 標準エラー出力に出力
  exit 1
else
  echo "eeyan"
fi
$ sh if.sh
Usage: if.sh file1 file2

$ sh if.sh ls file1
eeyan

case

unameを使ったOS判定の例

$ uname -sr
Darwin 15.6.0
#!/bin/bash

case `uname -sr` in
  Linux*)
    ls -l --full-time "$@";;
  FreeBSD* | NetBSD* | OpenBSD* | Darwin*) # Darwinから始まる任意の文字列
    ls -lT "$@";; # ここが実行される
  SunOS' '5.*)
    ls -E "$@";;
  *) # 何もひっとしない場合
    echo unknown OS 1&>2;;
esac
$ sh case.sh
total 88
-rw-r--r--  1 jumpei  staff   13  7  5 08:23:24 2018 2
-rw-r--r--  1 jumpei  staff  202  7  5 08:24:23 2018 case.sh
-rw-r--r--  1 jumpei  staff  158  7  5 08:18:11 2018 if.sh
-rw-r--r--  1 jumpei  staff   48  7  4 08:44:16 2018 sample.sh
-rw-r--r--  1 jumpei  staff   56  7  4 08:44:35 2018 sample2.sh
-rw-r--r--  1 jumpei  staff   87  7  4 08:52:39 2018 sample3.sh
-rw-r--r--  1 jumpei  staff   47  7  4 20:14:09 2018 sample4.sh
-rw-r--r--  1 jumpei  staff   55  7  4 20:13:57 2018 sample5.sh
-rw-r--r--  1 jumpei  staff  198  7  5 08:03:53 2018 sample6.sh
-rw-r--r--  1 jumpei  staff   69  7  5 07:52:58 2018 sample7.sh
-rw-r--r--  1 jumpei  staff   81  7  5 07:55:30 2018 sample8.sh

for

* はカレントディレクトリのファイル名

#!/bin/bash

for file in *; do
  echo $file
done
$ sh for.sh
LICENSE
README.md
for.sh
for_arg.sh
for_bash.sh
for_jot.sh
for_seq.sh
sample
sh

引数 $@ を使ったループ

#!/bin/bash

for arg in "$@";do
  echo $arg
done

for arg;do # 上と同じ意味
  echo $arg
done
$ sh for_arg.sh a b c
a
b
c
a
b
c

bashzshのプレース展開を使ったループ

#!/bin/bash

for i in {1..10};do
  echo $i
done
$ sh for_bash.sh
1
2
3
4
5
6
7
8
9
10

jotを使ったループ

#!/bin/bash

for i in `jot 10`;do
  echo $i
done
$ sh for_jot.sh
1
2
3
4
5
6
7
8
9
10
for i in `seq 1 10`;do
  echo $i
done
1
2
3
4
5
6
7
8
9
10

サブシェル

リストを()で囲むとサブシェルになり、サブシェルはもとのシェルとは別扱いで実行されます。サブシェルの中での変数の変更、umask値を変えても、サブシェルから出ると戻ります。この挙動を利用して、何らか変更をさせて終わったら、元の状態で処理もしたいなどのシチュエーションでは便利です。シェルスクリプトをリダイレクト、パイプでつないでも同じことはできます。

シェル変数 IFS: に変更して、set コマンドを実行すると : が削除され、 PATH の中身が $1 $2 $3 のように位置パラメータに設定されます。これらの変更はサブシェルを抜けると解除されます。

#!/bin/sh

echo "IFS=$IFS"

(
  IFS=:
  echo "IFS=$IFS"
  set $PATH
  echo $3
)

echo "IFS=$IFS"
$ sh sub_shell.sh
IFS=

IFS=:
/Users/jumpei/.anyenv/envs/rbenv/shims
IFS=

グループコマンド

リストを{}でかこむとグループコマンドと呼ばれる複合コマンドになります。リダイレクトしたり、パイプに接続したり、次のシェル関数の本体として利用できる。グループコマンドはサブシェルと違って、終わった後も影響があります。

コマンドの結果をまとめてlogfileにリダイレクトしている例。

#!/bin/sh

{
  hostname
  date
  who
} > logfile
$ sh group.sh
$ cat logfile
jumpei-no-MacBook-Pro-3.local
2018年 7月 6日 金曜日 08時24分53秒 JST
jumpei   console  Jul  4 07:49
jumpei   ttys001  Jul  4 07:49

シェル関数

シェル関数内の変数は引数渡しのため、位置パラメータ以外はグローバル変数になりますそのため、関数内はローカルにしたい場合は、{} ではなく ()を使って、サブシェルにすれば可能です

#!/bin/bash

greet()
{
  echo "Hello"
}

greet

greet2()
{
  echo $1 "Hello"
}

greet2 A
$ sh func.sh
Hello
A Hello

エラー系TIPS

変数の設定漏れ防止

set -u
変数を宣言していないものを使った時に終了ステータスを失敗(0以外)にしてくれる。

#!/bin/bash

VAL=foo
echo $VAL_TYPO
echo FINISH
$ sh sample.sh; echo $?

FINISH
0

$ cat sample2.sh
#!/bin/bash

set -u

VAL=foo
echo $VAL_TYPO
echo FINISH

$ sh sample2.sh; echo $?
sample2.sh: line 6: VAL_TYPO: unbound variable
1

エラーとしたくないケースを回避

途中まで処理して落としたい。デフォルト値が使える(${parameter:-word})

#!/bin/bash

set -u
if [ -z "${1:-}" ]; then
    echo "HOW TO hoge" >&2
    exit 2
fi
$ sh sample3.sh
HOW TO hoge

エラーになったら中断

set -e

何もつけないとエラーがあっても、最後まで実行される。しかも、ステータスコードも成功になる。

#!/bin/bash

FOO=$(ls --l)
echo $FOO
echo "OK"
$ sh sample4.sh; echo $?
ls: illegal option -- -
usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]

OK
0

エラー時に途中で終了し、ステータスコードも失敗が帰ってくるようになる

#!/bin/bash

set -e

FOO=$(ls --l)
echo $FOO
echo "OK"
$ sh sample5.sh; echo $?
ls: illegal option -- -
usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]
1

エラーを無視したいケース
grepで検索ヒットしない時にエラーステータスを返すので、それをいい感じにしたい。
以下のような感じにするか、コマンドでエラーとしないオプションとかを使うか
またはリストを使ったエラー無視などのパターンがあります。

#!/bin/bash

set -e

if find ./ | grep sample888.sh >/dev/null; then
  echo "Existing sample.sh"
else
  echo "Nothing sample.sh"
fi

rm a || true
rm a || : # trueの代わりに出来る

echo "OK"
$ sh sample6.sh; echo $?
Nothing sample.sh
rm: a: No such file or directory
rm: a: No such file or directory
OK
0

パイプライン内のエラーで中断する

set -eはパイプラインの一番右のコマンドのエラーは正しくエラーとしてくれるが、途中のコマンドのエラーは無視されます。

$ cat sample7.sh
#!/bin/bash

set -e

FOO=$(ls - l "$0" | wc -l )
echo $FOO
echo "OK"
$ sh sample7.sh; echo $?
ls: -: No such file or directory
ls: l: No such file or directory
1
OK
0

set -e -o pipefailをつけるとパイプライン中のエラーを検知してくれる。

#!/bin/bash

set -e -o pipefail

FOO=$(ls - l "$0" | wc -l )
echo $FOO
echo "OK"
$ sh sample8.sh; echo $?
ls: -: No such file or directory
ls: l: No such file or directory
1

エラーは標準エラー出力に出力しよう

#!/bin/bash

echo 'Expect stdout'
echo 'Expect stderr' 1>&2
$ sh ./output.sh >/dev/null
Expect stderr

$ sh ./output.sh 2>/dev/null
Expect stdout

bashに依存しているコマンドは#!/bin/bashと書かない

ubuntu上では以下のスクリプトは動きません。bashに依存しているなら、/bin/bashとしよう。

#!/bin/sh
list=( ($ls) )
echo $list

環境変数によって動かないを避ける

export LC_ALL=C

参考文献

AWS Serverless Application Model(SAM)を使ったサーバーレスアプリ構築

AWS Serverless Application Model(SAM)とは

f:id:tikasan0804:20180703082833p:plain

github.com

AWS Serverless Application Model (以降AWS SAMとする) は、AWS公式のサーバーレスアプリケーションを構築するためのフレームワークです。

以前の記事のServerlessでAWS Lambdaのデプロイをいい感じにする - ぺいで、Serverlessというつーるを紹介したのですが、SAMは、公式サポートなので、AWS以外のサーバーレス使う予定ない人は、基本的にSAMを使う方向で良さそうな気がします。

SAMとServerless Frameworkの違い

今回使ってみて、感じた違いについてまとめました。

Serverless Frameworkは、全部乗せの万能ツールという感じ。AWSに限らず様々なクラウドで統一の設定が使える。また、デプロイまでが本当サクッと出来る。SAMを使って思ったことですが、裏で暗黙的色々いい感じにやってるんだなと思った。
あと、コミュニティが盛んなこともあり、プラグインとかも色々出ているので、

SAMはAWSに特化していることもあり、AWSしか使わないなら、SAM使っとけばいいやんと使ってみて思った。

SAM使ってみる

どんなもんかをひとまず動かして体感してみる。インストールはpipで出来ます。

❯ python -V
Python 3.6.3
❯ pip install aws-sam-cli
...
❯ sam --version
SAM CLI, version 0.4.0

ちなみに、sam init を実行すると見本となるテンプレートが作られる。

SAM CLI のインストール - AWS Lambda

設定内容を追いかけてみる

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

上記の定義は、定型的に入れろとのことをそのまま鵜呑みにすると、バージョン指定という認識で良さそう。

Globals:
  Function:
    Timeout: 5

関数全体に対する設定。今回はタイムアウトまで5秒となっている。

Resources:
  HelloWorldFunction: # 関数名
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: hello-world/
      Handler: hello-world # Handler名 今回の場合、go build後のファイル名
      Runtime: go1.x
      Tracing: Active # X-Rayによるトラッキングの有効化
      Events: # どういうイベント(トリガー)を設定するか
        CatchAll:
          Type: Api
          Properties:
            Path: /hello
            Method: GET
      Environment: # 環境変数
        Variables:
          PARAM1: VALUE

関数の定義が書かれている。

Outputs:
  HelloWorldAPI:
    Description: "API Gateway endpoint URL for Prod environment for First Function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

  HelloWorldFunction:
    Description: "First Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn

  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

CloudFormationのOutputsセクションの定義。
出力 - AWS CloudFormation

main.go

package main

import (
    "errors"
    "fmt"
    "io/ioutil"
    "net/http"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

var (
    // DefaultHTTPGetAddress Default Address
    DefaultHTTPGetAddress = "https://checkip.amazonaws.com"

    // ErrNoIP No IP found in response
    ErrNoIP = errors.New("No IP in HTTP response")

    // ErrNon200Response non 200 status code in response
    ErrNon200Response = errors.New("Non 200 Response found")
)

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    resp, err := http.Get(DefaultHTTPGetAddress)
    if err != nil {
        return events.APIGatewayProxyResponse{}, err
    }

    if resp.StatusCode != 200 {
        return events.APIGatewayProxyResponse{}, ErrNon200Response
    }

    ip, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return events.APIGatewayProxyResponse{}, err
    }

    if len(ip) == 0 {
        return events.APIGatewayProxyResponse{}, ErrNoIP
    }

    return events.APIGatewayProxyResponse{
        Body:       fmt.Sprintf("Hello, %v", string(ip)),
        StatusCode: 200,
    }, nil
}

func main() {
    lambda.Start(handler)
}

今回の関数。

main_test.go

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/aws/aws-lambda-go/events"
)

func TestHandler(t *testing.T) {
    t.Run("Unable to get IP", func(t *testing.T) {
        DefaultHTTPGetAddress = "http://127.0.0.1:12345"

        _, err := handler(events.APIGatewayProxyRequest{})
        if err == nil {
            t.Fatal("Error failed to trigger with an invalid request")
        }
    })

    t.Run("Non 200 Response", func(t *testing.T) {
        ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.WriteHeader(500)
        }))
        defer ts.Close()

        DefaultHTTPGetAddress = ts.URL

        _, err := handler(events.APIGatewayProxyRequest{})
        if err != nil && err.Error() != ErrNon200Response.Error() {
            t.Fatalf("Error failed to trigger with an invalid HTTP response: %v", err)
        }
    })

    t.Run("Unable decode IP", func(t *testing.T) {
        ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.WriteHeader(500)
        }))
        defer ts.Close()

        DefaultHTTPGetAddress = ts.URL

        _, err := handler(events.APIGatewayProxyRequest{})
        if err == nil {
            t.Fatal("Error failed to trigger with an invalid HTTP response")
        }
    })

    t.Run("Successful Request", func(t *testing.T) {
        ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.WriteHeader(200)
            fmt.Fprintf(w, "127.0.0.1")
        }))
        defer ts.Close()

        DefaultHTTPGetAddress = ts.URL

        _, err := handler(events.APIGatewayProxyRequest{})
        if err != nil {
            t.Fatal("Everything should be ok")
        }
    })
}

関数に対するテスト。

Makefile

.PHONY: deps clean build

deps:
  go get -u ./...

clean: 
  rm -rf ./hello-world/hello-world
  
build:
  GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world

packageする

次に作成した関数をデプロイ用にパッケージ化します。

❯ aws s3 mb s3://hoge-fuga --region ap-northeast-1

パッケージの置き場所として、S3のバケットが必要なので、任意のバケットを用意するか、既にあるものを使います。作成するときは上のようなコマンドで作成する。

❯ sam package \                          
   --template-file template.yaml \        
   --output-template-file packaged.yaml \ 
   --s3-bucket hoge-fuga     

sam package コマンドで、作成したテンプレートから、パッケージングを実行する。デプロイに必要な情報は、今回の場合だと、outpu-template-fileで指定したpackaged.yamlで作成される。

デプロイ

❯ sam deploy \                   
   --template-file packaged.yaml \
   --stack-name sam-app \         
   --capabilities CAPABILITY_IAM  

sam deploy コマンドで実際にデプロイをします。--template-fileには先程出てきたpackaged.yamlを指定します。--stack-nameはCloudFormationのスタック名で、任意の名前を指定すれば良さそう。--capabilitiesはロールをいい感じにしておいて的なやつ。

f:id:tikasan0804:20180703081118p:plain デプロイ出来た!便利。

ローカル実行

SAMはDockerを使うことで、ローカル実行を可能にしている。

❯ sam local start-api
2018-07-03 08:19:00 Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
2018-07-03 08:19:00 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2018-07-03 08:19:00  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
2018-07-03 08:19:02 Invoking hello-world (go1.x)
2018-07-03 08:19:02 Found credentials in shared credentials file: ~/.aws/credentials

Fetching lambci/lambda:go1.x Docker container image......
2018-07-03 08:19:05 Mounting /Users/jumpei/go/src/github.com/pei0804/serverless-sample/sam/sam-app/hello-world as /var/task:ro inside runtime container
START RequestId: 69d1509b-f4cb-1597-862c-f4abceb3438b Version: $LATEST
END RequestId: 69d1509b-f4cb-1597-862c-f4abceb3438b
REPORT RequestId: 69d1509b-f4cb-1597-862c-f4abceb3438b  Duration: 1180.19 ms    Billed Duration: 1200 ms        Memory Size: 128 MB     Max Memory Used: 10 MB
2018-07-03 08:19:08 No Content-Type given. Defaulting to 'application/json'.
2018-07-03 08:19:08 127.0.0.1 - - [03/Jul/2018 08:19:08] "GET /hello HTTP/1.1" 200 -
❯ curl 127.0.0.1:3000/hello
Hello, 103.2.249.5

ログはCloudWatchライクなものが出てくる。これは強い。

ServerlessでAWS Lambdaのデプロイをいい感じにする

Serverless Frameworkとは

f:id:tikasan0804:20180627074913p:plain

github.com

通常AWS Lambdaを使う上で面倒な手順としてある。

  • 関数を作成する
  • AWS マネジメントコンソールからLambdaの管理画面にアクセスする
  • 関数をアップロードする(圧縮するとかもある)
  • トリガーとなるイベントの設定など

上に書いたような手間を解消してくれるのが今回紹介する「Serverless Framework」(以下Serverless)です。

http://tikasan.hatenablog.com/entry/2018/06/27/083316

AWSしか使わない人へ

Serverless Frameworkは様々なクラウドへのサーバーレスをアプリケーションの展開を考えている人にはうってつけですが、AWSしか使わないなら、AWS SAMがおすすめでした。

tikasan.hatenablog.com

インストール

Serverlessのインストールにはnode(v4以上)の環境が必要です。

❯ node -v
v10.0.0

❯ npm install -g serverless
...

+ serverless@1.27.3
added 310 packages in 19.063s

❯ serverless -v
1.27.3

IAMユーザーの作成

ServerlessはAWS APIを使って操作をします。なので、Adminstrator Acessを付与したServerless用のユーザーの作成をして、そのユーザーを使って色々な操作をすることになります。(defaultの自分のアカウントでも良いですが)

f:id:tikasan0804:20180627072752p:plain

f:id:tikasan0804:20180627072756p:plain

f:id:tikasan0804:20180627072800p:plain

❯ vim ~/.aws/credentials

[default]
aws_access_key_id =  hoge
aws_secret_access_key = fuga

[serverless]
aws_access_key_id = hoge2
aws_secret_access_key = fuga2
❯ vim ~/.aws/config

[default]
output = json
region = ap-northeast-1

[serverless]
output = json
region = ap-northeast-1

credentialsどうする問題は、公式が結構頑張って書いてくれてるので、そこ見たら良さそう。
Serverless Framework - AWS Lambda Guide - Credentials
今回は、専用のユーザーを作成するパターンでやっていきます。

事始め

さっそくServerlessのプロジェクトを作成してみましょう。ここからは長ったらしい serverless ではなく、短縮形の sls を使っていきます。slsには、templeateを作成する機能があり、これを使うと良い感じのsampleを見ることが出来ます。

選べるテンプレートは以下の通り。(すごい)

 Template "true" is not supported. Supported templates are: "aws-nodejs", "aws-nodejs-typescript", "aws-nodejs-ecma-script", "aws-python", "aws-python3", "aws-groovy-gradle", "aws-java-maven", "aws-java-gradle", "aws-kotlin-jvm-maven", "aws-kotlin-jvm-gradle", "aws-kotlin-nodejs-gradle", "aws-scala-sbt", "aws-csharp", "aws-fsharp", "aws-go", "aws-go-dep", "azure-nodejs", "fn-nodejs", "fn-go", "google-nodejs", "kubeless-python", "kubeless-nodejs", "openwhisk-java-maven", "openwhisk-nodejs", "openwhisk-php", "openwhisk-python", "openwhisk-swift", "spotinst-nodejs", "spotinst-python", "spotinst-ruby", "spotinst-java8", "webtasks-nodejs", "plugin" and "hello-world".
~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls create --template aws-go
Serverless: Generating boilerplate...
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.27.3
 -------'

Serverless: Successfully generated boilerplate for template: "aws-go"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ ls
Makefile       hello          serverless.yml world

では、何が書かれているか見てみましょう。

hello/main.go

よくあるhello worldサンプル

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
    Message string `json:"message"`
}

func Handler() (Response, error) {
    return Response{
        Message: "Go Serverless v1.0! Your function executed successfully!",
    }, nil
}

func main() {
    lambda.Start(Handler)
}

world/main.go

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
    Message string `json:"message"`
}

func Handler() (Response, error) {
    return Response{
        Message: "Okay so your other function also executed successfully!",
    }, nil
}

func main() {
    lambda.Start(Handler)
}

Makefile

デプロイ先でも動くようにというお気持ちの入ったbuildコマンドが書かれていました。

build:
  go get github.com/aws/aws-lambda-go/lambda
  env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go
  env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go

serverless.yml

slsに関する設定が色々書いてある。ひとまず、serviceの名前と、regionだけ変えておく。

# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!

service: hello <-- ここ自分のつけたい名前を適当につける
# service: aws-go # NOTE: update this with your service name

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
# frameworkVersion: "=X.X.X"

provider:
  name: aws
  runtime: go1.x

# you can overwrite defaults here
#  stage: dev
#  region: us-east-1
  region: ap-northeast-1 <-- 東京リージョンにしておく

# you can add statements to the Lambda function's IAM Role here
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
#        - "s3:ListBucket"
#      Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ]  }
#    - Effect: "Allow"
#      Action:
#        - "s3:PutObject"
#      Resource:
#        Fn::Join:
#          - ""
#          - - "arn:aws:s3:::"
#            - "Ref" : "ServerlessDeploymentBucket"
#            - "/*"

# you can define service wide environment variables here
#  environment:
#    variable1: value1

package:
 exclude:
   - ./**
 include:
   - ./bin/**

functions:
  hello:
    handler: bin/hello
  world:
    handler: bin/world

#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
# events:
#    events:
#      - http:
#          path: users/create
#          method: get
#      - s3: ${env:BUCKET}
#      - schedule: rate(10 minutes)
#      - sns: greeter-topic
#      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
#      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
#      - iot:
#          sql: "SELECT * FROM 'some_topic'"
#      - cloudwatchEvent:
#          event:
#            source:
#              - "aws.ec2"
#            detail-type:
#              - "EC2 Instance State-change Notification"
#            detail:
#              state:
#                - pending
#      - cloudwatchLog: '/aws/lambda/hello'
#      - cognitoUserPool:
#          pool: MyUserPool
#          trigger: PreSignUp

#    Define function environment variables here
#    environment:
#      variable2: value2

# you can add CloudFormation resource templates here
#resources:
#  Resources:
#    NewResource:
#      Type: AWS::S3::Bucket
#      Properties:
#        BucketName: my-new-bucket
#  Outputs:
#     NewOutput:
#       Description: "Description for the output"
#       Value: "Some output value"

デプロイと実行

~/go/src/github.com/pei0804/serverless-sample/serverless master* 16s
❯ make build
go get github.com/aws/aws-lambda-go/lambda
env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go
env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (4.5 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
........................
Serverless: Stack update finished...
Service Information
service: hello
stage: dev
region: ap-northeast-1
stack: hello-dev
api keys:
  None
endpoints:
  None
functions:
  hello: hello-dev-hello
  world: hello-dev-world

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls invoke -f hello
{
    "message": "Go Serverless v1.0! Your function executed successfully!"
}

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls invoke -f world
{
    "message": "Okay so your other function also executed successfully!"
}

~/go/src/github.com/pei0804/serverless-sample/serverless master*
❯ sls logs -f hello
START RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2 Version: $LATEST
END RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2
REPORT RequestId: 7dbebc56-7995-11e8-be4f-4788beaf19d2  Duration: 0.75 ms   Billed Duration: 100 ms     Memory Size: 1024 MB    Max Memory Used: 27 MB

動いているっぽい。この時点で既に便利!!

f:id:tikasan0804:20180627081459p:plain マネジメントコンソールからも確認出来た。

stageとかprofileをcliからいじる

設定ファイルにハードコーディングせずに、stageとかprofile変えたいなーっていうアレ。やり方としては、sreverless.ymlのproviderのところに設定を加えれば良い感じになる。
内容としては、--profileに指定がなければdefaultを使う。--stageに指定がなければdevという感じ。

service: hello

provider:
  name: aws
  runtime: go1.x
  profile: ${opt:profile, self:custom.defaultProfile}
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
custom:
  defaultStage: dev
  defaultProfile: default

package:
 exclude:
   - ./**
 include:
   - ./bin/**

functions:
  hello:
    handler: bin/hello
  world:
    handler: bin/world

cliでオプションで渡す。

~/go/src/github.com/pei0804/serverless-sample/serverless master* 15s
❯ sls deploy --profile serverless --stage production
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (4.5 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
........................
Serverless: Stack update finished...
Service Information
service: hello
stage: production
region: ap-northeast-1
stack: hello-production
api keys:
  None
endpoints:
  None
functions:
  hello: hello-production-hello
  world: hello-production-world

f:id:tikasan0804:20180627082914p:plain いい感じ!!

とりあえず、今回はここまで、次回はロジックとハンドラ分けたり、もうちょっと踏み込んだ内容やってみる。
前はapexを使ったりしていたけど、serverlessもかなり良さそう。

AWS Athena + Glueを使ったデータ分析(パーティション自動化)

ログは見れる状態にして価値が出る

f:id:tikasan0804:20180625084333p:plain

ログは雑に集めてるんだけど、見れる状態になっていないというのは、ストレージ料金を食ってるだけのゴミなので、破棄するか見れるようにするのですが、見れるようにするのは、そもそも結構大変だったりする。そこでFirehose + Athena + Glueを組み合わせて良い感じにしたいと思います。

データの保存形式をどうするか

色々ある。今回はJSONを使います。

Firehose ログをS3にまとめてPUTする

tikasan.hatenablog.com

Athena 集めた生ログに集計をかける

aws.amazon.com

AthenaはSQLでデータ集計を実行できるサーバーレスサービスです。使い方に関しては以下の公式ドキュメントを見ればわかります。
ざっくり説明すると、AthenaはCreate TableでS3のデータに対する参照を作成して、SQLクエリで集計が出来ます。なので、Drop TableやInsertクエリしたとしても、実データを操作することは出来ません。(READだけって割り切れるので安全といえば安全)
S3のデータをAmazon Athenaを使って分析する | Amazon Web Services ブログ

Glue テーブル自動作成

aws.amazon.com

AWS Glue は抽出、変換、ロード (ETL) を行う完全マネージド型のサービスで、お客様の分析用データの準備とロードを簡単にします。AWS マネジメントコンソールで数回クリックするだけで、ETL ジョブを作成および実行できます。AWS Glue では、AWS に保存されたデータを指定するだけで AWS Glue によるデータ検索が行われ、テーブル定義やスキーマなどの関連するメタデータAWS Glue データカタログに保存されます。カタログに保存されたデータは、すぐに検索、クエリ、ETL で使用できます。AWS Glue では、データ変換とデータのロードプロセスを実行するコードが生成されます。

そして、今回はこのAthenaのテーブルを作成するのが、Glueです。
Glueでよく出てくるETLというワードについて補足。これは、Extract Transform Loadのことです。一言でいうと、データ再集計という意味です。

Glueを使う理由

Athenaは検索をかける時に、パーティションを切ることで、スキャン範囲を狭めて、コストを軽減、検索完了時間を短縮が出来るのですが、このパーティションを切る作業が微妙に面倒。(パーティションの範囲が増えるごとに切らないといけない) そのあたりの面倒な作業をGlueは自動的に良い感じにしてくれる。

Glueクローラーの作成

f:id:tikasan0804:20180626201308p:plain

Add information about your crawler

Crawler name
クローラーの名前をつけます。今回は雑に「firehose」とします。

Add a data store

Choose a data store
JDBCかS3を選べます。今回は「S3」を選択します

Include path
データを保存しているS3の場所。今回でいうと、「s3://put-by-firehose/firehose」になる。

Choose an IAM role

Create an IAM roleを選択して、適当な名前を指定するといい感じに出来上がります。

内容は、AWSGlueServiceRoleという公式のロールと、以下のようなS3に対しての権限が付与されたロールが作成されました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::put-by-firehose/firehose/*"
            ]
        }
    ]
}

Frequency

Glueをどれくらいの頻度で動かすかを指定出来ます。今回は、テスト的に動かすだけなので、「Run on demand」にします。

Database

どういう名前でAthenaのdatabaseを作成するか、または既にあるdatabaseを使うかを選択出来ます。今回はdatabaseを作成したいので、Add databaseで「firehose」を作成して、それをそのまま指定しました。

Review

f:id:tikasan0804:20180625083153p:plain

実行

CrawlerをRunする

f:id:tikasan0804:20180625083325p:plain f:id:tikasan0804:20180625083345p:plain

Athenaで確認する

f:id:tikasan0804:20180626075639p:plain

f:id:tikasan0804:20180626075751p:plain

テーブルは確かにいい感じになってるけど。。。。名前がああああ。 この名前をいい感じに出来る方法知ってる人居たら教えてください。
現状、僕が知っている方法だと、定期的にパーティションを切るLambdaを実行するという方法しか思いつかない。(それGlue使わなくていいやんという話になる)

Amazon Kinesis Data Firehose 使い方

f:id:tikasan0804:20180624115811p:plain

Amazon Kinesis Data Firehose(長いのでFirehoseとします)

aws.amazon.com

どういうものかというのは、公式の素晴らしい説明文で大体わかります。

ストリーミングデータをデータストアや分析ツールに確実にロードする最も簡単な方法です。ストリーミングデータをキャプチャして変換し、Amazon S3Amazon Redshift、Amazon Elasticsearch Service、Splunk にロードして、現在お使いの既存のビジネスインテリジェンスツールやダッシュボードで、ほぼリアルタイムに分析することができます。

使ったユースケース

弊社のプロダクト(広告配信システム系)でまあまあなリクエストを受けているLambdaのログを見れる状態にしたいという場面で使いました。

Lambdaが各々でS3にログをPUTするぜみたいなことをしていた時に、ファイル数多すぎて、BQに入れれないし、Athenaで見るにしてもログ多すぎて検索終わるのを待つと、日が暮れる(正確にはエラーになった)となっていました。
そこに、Kinesis Firehose使って複数のLambdaが吐くログを固めてS3にPUTすることで、少し待てばAthenaでログを見れるようになりました。(ファイル数が減ってファイルIOが減ったから)

また、S3にPUTする時に、時間ごとにフォルダを自動で作ってくれるので、パーティションを切りたい時も同じルールを適用出来るのはいいなーと思いました。

Stream作成

Step 1: Name and source

Delivery stream name
ストリーム名。今回の設定値は「firehose」

Source

  • Direct PUT or other sources
    レコードを配信ストリームに直接送信する場合、またはAWS IoT、CloudWatchログ、CloudWatchイベントからレコードを送信する場合は、このオプションを選択します。
  • Kinesis stream
    Kinesis streamの流し先として、Firehoseを選べます。今回のユースケースでは使いませんでしたが、これも便利そう。

今回の設定値は、Lambdaから直接流す形なので、「Direct PUT or other sources」。

Step 2: Process records

Transform source records with AWS Lambda
Lambda 関数を呼び出して、受信した送信元データを変換してから送信先に配信できます。Kinesis Data Firehose のデータ変換は、配信ストリームの作成時に有効にすることができます。 とあるように、流れてくるデータを何か手を加えたい時は、これを使うと良さそう。 Amazon Kinesis Data Firehose のデータ変換 - Amazon Kinesis Firehose

今回の設定値は、「Disabled」。

Convert record format
データのフォーマットをJSONから何かへみたいな感じの変換が出来る。

以下の2つが用意されてる。また、AWS Glueとも連携が出来るようです。(別記事で調査する予定) - Apache Parquet - Apache ORC

今回の設定値は、「Disabled」。

Step 3: Choose destination

Select destination
保存先を設定する。保存先として以下の4つがある。

今回の設定値は、「Amazon S3」。

S3 destination

  • S3 bucket
    保存先のS3を選ぶ。
  • Prefix
    FirehoseがPUTする時のフォルダ名と認識すればおk。

今回は、Create newして、「put-by-firehose」というバケットを作成。また、Prefixは「firehose/」とした。こうすることで、put-by-firehose/firehose/firehoseがよしなにという形にしてくれる。

Step 4: Configure settings

S3 buffer conditions
受信レコードをS3バケットに転送する前にバッファリングしてくれる。その時の設定をする。条件のいずれかが満たされると、PUTされる。 - Buffer size
1-128 MBの間でバッファするサイズを決めれる
- Buffer interval
60-900秒でPUTする間隔を設定出来る

今回は、どちらも最小値の「1MB」と「60秒」とする。もし、リアルタイム制が要求されない場合は、もっと高い数値を設定することになる。

S3 compression and encryption

  • S3 compression
    どういう圧縮形式でS3にPUTするかというもの。
  • S3 encryption S3上のデータを暗号化するか

今回は、「GZIP」で圧縮し、暗号化については「Disabled」にした。

Error logging
エラーをCloudWatchログを出力する。

今回は使わないので、「Disabled」とした。

IAM role

Create newして、いい感じにしてくれます。デフォで作成される権限は以下のような感じ。まあ、公式によるものだから、オーバーな権限はないし最低限になっている。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "glue:GetTableVersions"
      ],
      "Resource": "*"
    },
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "s3:AbortMultipartUpload",
        "s3:GetBucketLocation",
        "s3:GetObject",
        "s3:ListBucket",
        "s3:ListBucketMultipartUploads",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::put-by-firehose",
        "arn:aws:s3:::put-by-firehose/*",
        "arn:aws:s3:::%FIREHOSE_BUCKET_NAME%",
        "arn:aws:s3:::%FIREHOSE_BUCKET_NAME%/*"
      ]
    },
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "lambda:InvokeFunction",
        "lambda:GetFunctionConfiguration"
      ],
      "Resource": "arn:aws:lambda:ap-northeast-1:851669633371:function:%FIREHOSE_DEFAULT_FUNCTION%:%FIREHOSE_DEFAULT_VERSION%"
    },
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:ap-northeast-1:hoge:log-group:/aws/kinesisfirehose/:log-stream:*"
      ]
    },
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "kinesis:DescribeStream",
        "kinesis:GetShardIterator",
        "kinesis:GetRecords"
      ],
      "Resource": "arn:aws:kinesis:ap-northeast-1:hoge:stream/%FIREHOSE_STREAM_NAME%"
    },
    {
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt"
      ],
      "Resource": [
        "arn:aws:kms:region:accountid:key/%SSE_KEY_ARN%"
      ],
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "kinesis.%REGION_NAME%.amazonaws.com"
        },
        "StringLike": {
          "kms:EncryptionContext:aws:kinesis:arn": "arn:aws:kinesis:%REGION_NAME%:hoge:stream/%FIREHOSE_STREAM_NAME%"
        }
      }
    }
  ]
}

Step 5: Review

f:id:tikasan0804:20180624141537p:plain

StatusがActiveなったら使える状態になります。
f:id:tikasan0804:20180624141928p:plain

Lambda -> Firehose

ロール作成

今回は、LambdaからFirehoseへPUTしたいので、AWSLambdaMicroserviceExecutionRoleAWSLambdaBasicExecutionRoleのロール以外に、インラインで以下のような雑なロールを付加しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "firehose:PutRecord",
                "firehose:PutRecordBatch"
            ],
            "Resource": "arn:aws:firehose:*:*:deliverystream/*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "firehose:DescribeDeliveryStream",
                "firehose:ListDeliveryStreams"
            ],
            "Resource": "*"
        }
    ]
}

関数作成

import boto3


def lambda_handler(event, context):
    firehose = boto3.client('firehose')
    firehose.put_record(
        DeliveryStreamName='firehose',
        Record={
            'Data': b'{"user_id": "a", "event": "AAA"}\n'
        }
    )
    firehose.put_record(
        DeliveryStreamName='firehose',
        Record={
            'Data': b'{"user_id": "b", "event": "BBB"}\n'
        }
    )
    firehose.put_record(
        DeliveryStreamName='firehose',
        Record={
            'Data': b'{"user_id": "c", "event": "CCC"}\n'
        }
    )

今回はLambdaから叩いていますが、もちろんローカルからの実行でも動きます。

PUTしてみる

Lambdaを実行すると、以下のようにサクッと出来上がりました!便利! PUTするだけで、ある程度固めてくれるのは相当ありがたい。

put-by-firehose/firehose/2018/06/24/05/firehose-1-2018-06-24-05-47-40-34054ccb-1fa3-47d6-b05f-749b51d032a5.gz

f:id:tikasan0804:20180624145709p:plain

集めたデータどうする

記事の冒頭でも書きましたが、Athenaでいい感じに見れようにしたりも出来るんですが、体力が尽きたので、次の記事で紹介することにします。