Amazon EC2 Container Service 実践入門の前に

この記事はサーバーワークス Advent Calendar 2016の24日目の記事です。
qiita.com


お久しぶりの更新です。お元気でしたか?サーバーワークスの中の人でもOBでもありませんが、個人的に仲良くしていただいているので、参加してみることにしました。中の人達と違って芸人的な要素はないですが、お手柔らかによろしくお願いします。

はじめに

この題材を選んだのは、最近ようやくDockerを使って何かをしてみようかと思い始めたのがきっかけです。ハマるポイントがあればサービスでも使っていきたいなと思っているので、基礎から始めたいと思います。

そして、サーバーワークスさんとは切っても切れないAWS、自分もとある書籍を共著させていただいているAWSということで、「Amazon EC2 Container Service (ECS)」をほぼ初めて触ってみます。

Amazon EC2 Container Service (ECS)

EC2上で利用できるDockerのクラスタリングサービス。ELBやAutoScalingなど他のサービスとの組み合わせで、いい感じにDockerを用いたシステムを構築することができます。

ひとまずGetting Startedをやってみる

リポジトリ設定

何はなくとも、Management ConsoleでECSのメニューを開きましょう。

f:id:matetsu:20161223174527p:plain

このような画面が出てきますので、「Get Started」しちゃいましょう。(注)言語は英語になっています

f:id:matetsu:20161223174843p:plain

サンプルアプリを使ったECSとイメージ配置用のプライベートリポジトリであるECR(EC2 Contaier Registry)の設置をしてしまうか?と聞かれるので、チェックを入れたまま進みましょう。

f:id:matetsu:20161223175902p:plain

リポジトリ名を聞かれるので、好きな名前を入力します。ここでは、namespaceとして「matetsu」を、リポジトリ名としてはひとまず「ecs-sample」として作っています。

f:id:matetsu:20161223181405p:plain

設定が完了すると、ECRへのログイン方法やイメージのビルド、からのECRへのイメージの登録方法が表示されます。作業をしているPCでDockerやAWS CLIのインストールや初期設定がしてない場合は、ここでやってしまいましょう(本記事では省略)。AWS CLIは特に設定済みであっても、なるべく常に最新版を利用するようにしておきましょう。

表示された手順に従ってコマンドを実行していきましょう。

イメージの作成とアップロード (ちょっとCLI操作)

(1) docker loginコマンド(認証情報付き)の取得
$ aws ecr get-login --region ap-northeast-1
docker login -u [ユーザ名] -p [めちゃくちゃ長いパスワード] -e none https://[ACCOUNT_ID].dkr.ecr.ap-northeast-1.amazonaws.com

こんな感じで出力されますので、出力をコピーして実行しましょう。

(2) docker loginコマンドの実行
$ docker login -u [ユーザ名] -p [めちゃくちゃ長いパスワード] -e none https://[ACCOUNT_ID].dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded

「Login Succeeded」と出力されればログイン成功です。

(3) Dockerfileからイメージを作成


超簡易的だけど、Dockerfileはこんな感じ。

FROM amazonlinux:latest

RUN yum update -y && \
      yum install nginx -y && \
      date +%s > /usr/share/nginx/html/index.html && \
      yum clean all && \
      rm -rf /var/cache/yum

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

これを使ってイメージ作成。

$ docker build -t matetsu/ecs-sample .
Sending build context to Docker daemon 6.144 kB
Step 1 : FROM amazonlinux:latest
latest: Pulling from library/amazonlinux
8e3fa21c4cc4: Pull complete
Digest: sha256:f1d4ae3f7261a72e98c6ebefe9985cf10a0ea5bd762585a43e0700ed99863807
Status: Downloaded newer image for amazonlinux:latest
 ---> 5b52b314511a
Step 2 : RUN yum update -y &&       yum install nginx -y &&       date +%s > /usr/share/nginx/html/index.html &&       yum clean all &&       rm -rf /var/cache/yum
 ---> Running in b889b04f755b
(yum途中経過省略)
 ---> bad3749e433c
Removing intermediate container b889b04f755b
Step 3 : EXPOSE 80
 ---> Running in a6ee101a6b56
 ---> 44a602686c42
Removing intermediate container a6ee101a6b56
Step 4 : CMD nginx -g daemon off;
 ---> Running in 7e740836ca4e
 ---> 8c9087605ba1
Removing intermediate container 7e740836ca4e
Successfully built 8c9087605ba1

$ docker images                                                                                                                                                                  [~/works/.../ecs/test1]
REPOSITORY              TAG                 IMAGE ID            CREATED              SIZE
matetsu/ecs-sample      latest              8c9087605ba1        About a minute ago   385.1 MB
amazonlinux             latest              5b52b314511a        4 weeks ago          292.3 MB
(4) イメージのタグ付け
$ docker tag matetsu/ecs-sample:latest [ACCOUNT_ID].dkr.ecr.ap-northeast-1.amazonaws.com/matetsu/ecs-sample:latest
(5) イメージをリポジトリへpush
$ docker push [ACCOUNT_ID].dkr.ecr.ap-northeast-1.amazonaws.com/matetsu/ecs-sample:latest

ECSに関連する項目の設定

再びGUIでの操作に戻ってきました。次はTask Definitionの設定です。

f:id:matetsu:20161223215532p:plain

「Advanced Option」なるものもあるようですが、今回はすべてデフォルトで行ってみようと思います(ここで設定しているのはDocker Composeのcompoe.ymlで定義しているものと同じような感じなんですかね)。次はServiceの設定です。

f:id:matetsu:20161223220734p:plain

基本はデフォルトですが、ELBは使っておきたいので「No ELB」から「sample-app:80」に変更しておきます。次はクラスタの設定です。

f:id:matetsu:20161223221124p:plain

基本デフォルトで、コンテナインスタンス(Docker Host)用のRoleは新しく作成することにしています。これで設定は終わりで、後は設置項目の確認とLaunch待ちです。

f:id:matetsu:20161223222331p:plain

ECSとEC2周りのリソースが出来上がるとこんな感じになります。

出来上がったリソースなどの確認

サービスの確認

もろもろのリソース作成が完了下画面に、「View Service」なんてボタンがあるわけですので、早速確認してみましょう。

f:id:matetsu:20161223222950p:plain

Serviceの詳細ページが表示され、Taskが実行中であることが確認できます。

クラスタの確認

他にも、クラスタの一覧はこんな感じで、

f:id:matetsu:20161223223623p:plain

クラスタの詳細はこんな感じになっています。

f:id:matetsu:20161223223736p:plain

実際にECSで稼働しているwebページにアクセスしてみる

かんたんに確認するために、起動されたELBのDNS名にアクセスしてみましょう。

f:id:matetsu:20161223224113p:plain

イメージを作成したときのdate +%sの実行結果であろう値が確認できました。

Getting Startedを使ったからナビがあってそれに従えば必要なものがすべて準備されたのでよかったけど、ナビがないとなると結構大変な作業ですね、これ。。。

rolling updateをしてみる

ただ動かすだけだと何もしていないことになるので、まずはrolling updateを試してみましょう。

rolling updateをする場合は、ServiceのDeployment OptionにあるminimumHealthyPercentmaximumPercentが肝になるようです。 (参照: AWS Black Belt Online Seminar 2016 Amazon EC2 Container Service /
Amazon ECS launches new deployment capabilities; CloudWatch metrics; Singapore and Frankfurt regions | AWS Compute Blog )

  • min:50% / max: 100%

- リソースを効率的に使いながら、まずはTaskをDesiredの半分に減らして新規TaskをDesiredの半分数追加し、残りの半分を入れ替える

  • min: 100% / max: 200%

- Desiredと同量のTaskを一気に追加し、倍量になったところで古い方を一気に落とす

ひとまず、1つしかTaskを動かしていないので、後者の富豪型(現状の設定もそれ)でやってみることにする

Docket Imageの更新

更新されたことがわかるようにイメージをこのようにする。

FROM matetsu/ecs-sample:latest

RUN yum update -y && \
      yum install nginx -y && \
      date +%s > /usr/share/nginx/html/index.html && \
      echo " updated" >> /usr/share/nginx/html/index.html && \
      yum clean all && \
      rm -rf /var/cache/yum

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

timestampが変更になるのと、「 updated」をいう文言を追加している。

$ docker build -t matetsu/ecs-sample:0.2.0 .
(略)

新イメージができたので、先程のようにECRにpushする。

$ docker tag matetsu/ecs-sample:0.2.0 [ACCOUNT_ID].dkr.ecr.ap-northeast-1.amazonaws.com/matetsu/ecs-sample:0.2.0                                                              [~/works/.../ecs/test1]
$ docker push [ACCOUNT_ID].dkr.ecr.ap-northeast-1.amazonaws.com/matetsu/ecs-sample:0.2.0

Task Definitionの新revisionの追加

Task Definisionに新しいRevisionを追加することでrolling updateを実行することができる。が、Container Instanceが1台のままだとポートが衝突してしまうためにコンテナを起動できない。

f:id:matetsu:20161224003650p:plain

まずはInstanceのDesired Countを2にしておく。

f:id:matetsu:20161223233817p:plain

このあとでTask DefinitionのRevision追加する。対象のTask Definitionを選択して「Create new revision」。

f:id:matetsu:20161223234139p:plain

Containerの部分のリンクをクリックすると、コンテナイメージの指定などを変更できる。

f:id:matetsu:20161224004824p:plain

イメージのタグを変更し、revisionの変わったTask Definitionを作成する。

f:id:matetsu:20161223234333p:plain

Serviceの更新

追加したTask DefinitionのRevisionを適用するために、Serviceの更新を行います。

f:id:matetsu:20161223234439p:plain

Task Definitionを「console-sample-app-static:[最新のrevision]」(色々と試行錯誤で作り直したので2ではない)に変更して適用します。Min/Maxは富豪型にするため、変更していません。

f:id:matetsu:20161223234725p:plain

これで、しばらくまっていると、新旧両方へのアクセスができる状態になり、更に待つと古い方は表示されなくなります。この間にサービスダウンは発生しないので、実際のサービスに適用するときもうれしいですね。

f:id:matetsu:20161224010555p:plain

今回は状況ややっていることを確認しながらの実施だったので手動で行いましたが、手動での適用は事故を起こしたり時間がかかってしまうので、実際に運用する場合はコマンドラインスクリプトなどを用いて事故が起こらないようにする必要があるでしょうね、

最後に

まだまだほんの初歩的な使い方をしてみただけで全然実践的な内容になっていませんが、まずは触ってみることでDockerイメージさえ整えば何かしらサービスにおm利用できそうなイメージが湧いてきました。もう少し深く使ってみて、続編がかけるようにしたいと思います。ECSについてはriywoさんのBlackBeltのスライド、Dockerについては前佛さんのスライドを見てもっと勉強します。

P.S. 反省点

ほとんどDockerを触ってこなかったのでここまでやるだけでも結構大変だったのですが、初心者の割にDockerの基本的な部分をすっ飛ばして書いてしまったのは反省してます。また、基本的にはGetting Startedやっただけで、おまけ的にrolling updateを付け足した感じになってしまったのも、コンテンツとしてはちょっと物足りな鋳物になってしまったと思っています


ただ、明日の最終日は最近話題のkokexaさんが鉄板ネタで攻めてきてくださるので、素敵な締めに期待したいと思います。

JAWS-UG初心者支部 第4回勉強会でS3/CloudFront/Route 53について発表してきました #jawsug_bgnr

2016年2月16日にD2Cさんで開催された「JAWS-UG初心者支部 第4回勉強会」で発表をしてまいりました。

今回の初心者支部はテーマとして『Amazon Web Services実践入門』(緑本というらしいです)の著者に会える勉強会ということで、呼んでいただきました。今回は著者のうち、ハンズラボの今井さん、サーバーワークスのぎょりこと永淵さん、私の3名が召喚されました。

Amazon Web Services実践入門って何?」という方は、以下のリンクをクリックして、カートに入れて購入をしていただけると幸いです。

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Amazon Web Services実践入門 (WEB+DB PRESS plus)

発表: 「Simple Front 53」

緑本でも担当したS3、CloudFront、Route 53の3つのサービスについて概要と利用例などを説明しました。Certificate ManagerやWAFについてもさらっと、本当にさらっとですが触れました。

LTでも緊張するのに、通常の発表が久しぶりすぎてめちゃめちゃ緊張しました。じぶんでも何言ってるかわからなくなってたので、聞いてる方たちはもっとわからなかっただろうな。

そして、発表で使った資料はこちらです。

本の内容に近づけつつ、応用的なこともあったら嬉しいかなという気持ちだったのですが、Simple Iconの説明なしで図を出したのはハードルが高かったようで、反省しました。。初心者支部に集まる人達がどのくらい初心者かを先に確認しておくべきでしたね。

自分でも普段使わないサービスのアイコンが出てきてもわからないし、"初心者"支部をうたっているのだからもっと聞き手のことをリサーチしておくべきでした。公開した資料にはそのあたりのことを踏まえて説明を追記してあるので、発表中に???????となってしまった方々は、改めて見ていただけると嬉しいです。

その他の発表

については、他の方々のブログにまとまっていますので、そちらを参考にしていただければと。
(ちゃんと聞いてたよ!)

今井さんは、所属するハンズラボでのEC2の利用方法から、ご自身のAWSに関する勉強法といった流れで、非常にためになる内容でした。今井さんの堅実さが伝わってきました。

ぎょりさんは請求周りについての内容で、盛り込みすぎて時間が全然足りなくなるというアクシデントに見舞われました。が、やはり発表うまいし、半ハンズオン的な発表とか面白かったです。裸足だったし。

まとめ

− 聞き手のレベルをちゃんと把握しておきましょう
− 発表はちゃんと練習しましょう
− プロジェクタには予め接続テストをしておきましょう(私はしておいたので、問題ありませんでしたw)
− LTerの方々から Amazon Web Services実践入門は好評を頂いていたようです

(写真とってなくてアイキャッチ画像がない。。。というわけで、ほんの表紙でご勘弁を)

そう、今日は肉の日。だから振り返ってみる。

早いもので、某中井夫妻(@nakansuke & @Nagafichik)と3人で開催している肉会も2年を超えました。この肉会のおかげで、毎月29日頃にうまい肉を喰らい、幸せな気持ちになっております。

(つい30時間前くらいまで完全に忘れていましたが)そろそろ3人で肉会のまとめをしようということになり、各々のブログで肉の日である2月9日に発表しようということになりましたので、唐突ではありますが肉ブログです。

いろいろあってなかなか選抜には苦労しましたが、独断と偏見のTOP3を発表してみたいと思います。

(ドラムロール)

第3位 黒牛 / 2014年10月29日来店

tabelog.com

この月は前の月に開催できず、2ヶ月分まとめての開催ということで普段財布の紐が固めの私も奮発したなあ。
高級なシャトーブリアンが初お目見えしたり、結構金銭感覚がおかしかったとは思うが、うまい肉のためなら多少は奮発する肉会のベースになったような気がする思い出の回でもある。いい肉は高いからうまいとは限らないこともありますが、ここは高いけどうまかったので満足。東京は金を払えばうまいもんが食える。田舎者の学びでした。
(ビールとマッコリのちゃんぽんで結構気持ち悪くなっていたのは気づかれていなかったはず。)

(ドラムロール)

第2位 生粋 / 2015年11月29日

tabelog.com

よろにく系の店のひとつ。お上品な感じのコースで、いい肉を適度にいただける店。私もいい年になってきたので、サシの多い肉をがっつり大量に食べることはできませんが、生粋さんは適量で満足度も高い。〆の冷麺もちょうどよい。デザートのかき氷にはちょっとびっくりしつつ、でもコースを締めくくるのに調度よい感じ。おいしくてちょうどよい。よい。高級な感じ。

(ドラムロールと照明くるくる)

第1位! 房家 本郷三丁目店 / 2014年11月29日

tabelog.com

栄えある第1位は、本郷三丁目にある房家さん。ここは(他と比較して)お手頃な感じで高級肉がいただけるので非常に満足度が高い。いろんな部位をいろんなタレなどでいただけるのもよいです。変わり種としては限定品(?)のサクラユッケがウマウマです(食ったよね)。肉は数秒焼くだけでこれほどにもうまくなるのかと感動を呼ぶこと間違い無し。そろそろ再訪したい。

お店の感じも気取った感じではないので気軽に(予約すれば)行ける感じもよいです。


別枠 和飲和ん / 何度も

tabelog.com

中井(夫)さん行きつけの店。二人の結婚祝いもここでやったし、ここではないけど姉妹店の和ん通桶T氏どんまいこ氏と5人で行ったことが肉会の始まりだったりもするので、思い入れのある店。肉も酒もうまいし、常連な中井さんがいるおかげで普通よりは良いサービスを受けられていると思う。肉もうまいけど、卵かけご飯もうまいから、食うべし。


その他捨てがたいとこ

NO MEAT, NO LIFEやジャンボ、格之進、(ちょっと系統が違うけど)ひつじやあたりも捨てがたい。いやあ、ホントいい店に行ってるな。

その他の店

その他の店については、夫妻が書いてくれているはず。

nakansuke.hatenablog.com
gyori.hatenablog.com

まとめ

うまい肉を食うと幸せになれます。年齢が上がってきてたくさんは食えないけど、いい肉を少しずつ、かつ大胆に食える肉会。皆様にもおすすめです。

気づいたらTOP3ではない感じになってしまいましたが、気にしない。

Amazon Kinesis Streamsを使ってみた

11日目、というより11記事目といったほうがいいですかね。ネタを思いつかないので、基本に立ち返って「使ってみた」系にしてみました。

そこそこの期間AWSを利用しているのですが、業務に関係無かったりするとなかなか触れないサービスもあったりします。そういうものを触っていこうというのも今回のAdventCalendar的更新のモチベーションの1つだったはずなので、わざわざ簡単なシステムに落とさなくてもいいのではと思った次第です。

というわけで、Kinesisを使ったことがなかったのでとりあえずはじめの一歩的な内容をば。(間違っていることもあるかも)

Kinesis Streamの作成

今回扱うのはKinesis Streamの方です。今回はFirehose扱いませんが、バージニアオレゴンアイルランドの3リージョンでのみ利用可能となっています。

f:id:matetsu:20151213195311p:plain

それでは始めましょう。Let's 「Create Stream」!

f:id:matetsu:20151213213618p:plain

名前はとりあえず「stream-test」とでもしておきます。

はじめてなので、「Help me decide how many shards I need」にチェックを入れて助けを請いましょう。とりあえずは平均レコードサイズは1KBとして最大書き込み/秒は適当に100としておきましょうか。Consumerアプリケーション数は1とします。

すると、Shard数は「1」で良いでしょうと言った感じで出てきます。

f:id:matetsu:20151213211401p:plain

では、お言葉に甘えて「1」を利用させていただきましょう。(ドキュメントのシャードの項を読めばたいんいつシャードあたりの性能はわかりますし、Shard数を入れると下にキャパシティなどが表示されるのでそれを見ながら調整してもいいんでしょうねw)

f:id:matetsu:20151213213211p:plain

「Create」ボタンを押したら、Statusが「Active」になるまで待ちましょう。

f:id:matetsu:20151213213358p:plain

ACTIVEになるとこんな感じ。

f:id:matetsu:20151213213420p:plain

streamができあがりましたので、データ(レコード)を入れてみましょう。

Streamにレコードを送信する

KinesisではStreamにデータを送信する役割をもったノードやアプリケーションをProducerと呼びます。

まずは簡易的にレコードを送信してみたいので、kinesis-catを利用してみます。

$ gem install kinesis_cat
$ echo '{"key1": "val1", "key2": "val2"}' | kinesis-cat --stream-name stream-test

では送信したレコードを確認してみましょう。確認するためには、CLIを使います。

まずshardの情報を取得して、シャードの位置を確認する必要があります。kinesis get-shard-iteratorコマンドで取得したShardIteratorは300000msec以内に利用しないとExpireされますので、初めてでもたついていると苦労します。

$ aws kinesis describe-stream --stream-name stream-test
{
    "StreamDescription": {
        "StreamStatus": "ACTIVE",
        "StreamName": "stream-test",
        "StreamARN": "arn:aws:kinesis:ap-northeast-1:ACCOUNT_NO:stream/stream-test",
        "Shards": [
            {
                "ShardId": "shardId-000000000000",
                "HashKeyRange": {
                    "EndingHashKey": "340282366920938463463374607431768211455",
                    "StartingHashKey": "0"
                },
                "SequenceNumberRange": {
                    "StartingSequenceNumber": "49557238107343677981012040936118820808140350529483046914"
                }
            }
        ]
    }
}
$ aws kinesis get-shard-iterator --shard-id shardId-000000000000 --shard-iterator-type TRIM_HORIZON --stream-name stream-test
{
    "ShardIterator": "AAAAAAAAAAFPuPSOFFd7SOl/TA2jlaJQHCJjOdBOtaqG/R/vgaY9LpL+u7QofNeFxXHlR54VPmcrEcTOGQocib2Gcd6xGwXpknZSPEVHw0mcDUPnFYaEuf1O61Yacn4bPJNj86SWT033nlXkycojKM0+0td08FCfQ/Behdp2SloPr7hIehAUMZeYh3yU8t0n2NvIw+tbvM4WT32X2fn7/kya87tVhLLM"
}
$ aws kinesis get-records --shard-iterator AAAAAAAAAAHTBivnV5Rk0dGwvKd0PnA2ntjdqYX6xsO3wtkTP/37T63C5AdZTbwfONrXge1PT43OKkkrbx1Cga6lbE+VMNzBwHoxJW9XZ35vUpkhkaTq1c62HSALUp3B+AM8cJaRtu3gpNWi2emSta+lGxLeO2CgT8fdq9yy5uZFAlkHYgilgDAbxnHl6Ps3+6g38jdXP4IsXjG4HbrYHf6P/bSUeSru
{
    "Records": [
        {
            "PartitionKey": "1450012611",
            "Data": "eyJrZXkxIjoidmFsMSIsImtleTIiOiJ2YWwyIn0=",
            "SequenceNumber": "49557238107343677981012040936505677070417222287059058690"
        }
    ],
    "NextShardIterator": "AAAAAAAAAAGlPk98twtF2KpCnwlOadDjIgSDr60meZq9qjV88WLzDWPpuplichv5qALBFXZ2igTDMxRBy15Tchl6mlFuNGohx6Nu0MAQ07BB4Mv+NWmmLsxXHeGFd19tFrcABk7k+nKEVnTZsV1qDiQf+5Y32bCi4Wm5xvsGXPie9QUBfi6zrLwViHNVM0FNfNWGdOkliOxXxALtl731nVv6neWhVvJg",
    "MillisBehindLatest": 685000
}

ShardIteratorの取得とRecordsの取得は、合わせてこんな感じにしたほうがいいですかね。(ドキュメントにも近いことが書いてある)

$ aws kinesis get-records --shard-iterator $(aws kinesis get-shard-iterator --shard-id shardId-000000000000 --shard-iterator-type TRIM_HORIZON --stream-name stream-test | jq -r .ShardIterator)

また、表示されたDataはBase64エンコードされているので、

$ aws kinesis get-records --shard-iterator $(aws kinesis get-shard-iterator --shard-id shardId-000000000000 --shard-iterator-type TRIM_HORIZON --stream-name stream-test | jq -r .ShardIterator) | jq -r ".Records[0].Data" | base64 -D
{"key1":"val1","key2":"val2"}

とするとデコードされた状態で確認することができます。

再度レコードを登録して、重複なく取得するためには、NextShardIteratorを利用します。

$ aws kinesis get-records --shard-iterator AAAAAAAAAAGlPk98twtF2KpCnwlOadDjIgSDr60meZq9qjV88WLzDWPpuplichv5qALBFXZ2igTDMxRBy15Tchl6mlFuNGohx6Nu0MAQ07BB4Mv+NWmmLsxXHeGFd19tFrcABk7k+nKEVnTZsV1qDiQf+5Y32bCi4Wm5xvsGXPie9QUBfi6zrLwViHNVM0FNfNWGdOkliOxXxALtl731nVv6neWhVvJg --stream-name stream-test
{
    "Records": [],
    "NextShardIterator": "AAAAAAAAAAGeum3ukha5gAZXqSkErxc+CUW0LhCIWv1iJ65F6vEvKyIC//McDjma4EKRfGNZqOFOhGCn4EHMTFyjwmbxzl35Pv6vY6a43OBjaQlZ7/zUfk435y8ULCtc0M6w4J9U9s5ttYdI9DIOZ+JlKL0KKgaze13nLmo2PpmCtKglyQ4PB4nE761Etyv3j8ZgzvorRfIW9/pmBPkn6/Z7E5j5NdSl",
    "MillisBehindLatest": 0
}

と言った感じで、今は何も次に登録していないので、空のRecordsが返ってきます。

急いで新たなレコードを登録して、先ほど表示されたNextShardIteratorを指定してみましょう。

$ echo '{"key1": "val1", "key2": "val2"}' | kinesis-cat --stream-name stream-test
$ aws kinesis get-records --shard-iterator AAAAAAAAAAGeum3ukha5gAZXqSkErxc+CUW0LhCIWv1iJ65F6vEvKyIC//McDjma4EKRfGNZqOFOhGCn4EHMTFyjwmbxzl35Pv6vY6a43OBjaQlZ7/zUfk435y8ULCtc0M6w4J9U9s5ttYdI9DIOZ+JlKL0KKgaze13nLmo2PpmCtKglyQ4PB4nE761Etyv3j8ZgzvorRfIW9/pmBPkn6/Z7E5j5NdSl
{
    "Records": [
        {
            "PartitionKey": "1450014952",
            "Data": "eyJrZXkxIjoidmFsMSIsImtleTIiOiJ2YWwyIn0=",
            "SequenceNumber": "49557238107343677981012040936509303847876227046878216194"
        }
    ],
    "NextShardIterator": "AAAAAAAAAAF+SbH2cLWMq0ytQLuaMc3ZSH7pNMBCU2hGycDaMlZZBIh5E8p9fVHeOsBidhoYPJ1DzoFUdDFCbLG+H2WHcqD9tcGIQN1+TR2cVrnTOZwijnlccd1XoVQDrDcQ5dIMwZIHMQggvG5i+eEVobCpmqWbicoE0H1XcrMfbSJ1KTFAsYVLJ3VBpsdC0UR9ofRudf2ZF/lk2G2W6BotkjFrvLEn",
    "MillisBehindLatest": 37000
}

はい、うまくいきました。とりあえず、KinesisのStreamにレコードを登録して、取得するところを見てみることができました。

おわりに

とりあえず動かしてみたレベルでしか無いので、次回はより実践的な内容で使ってみたいと思います。実際の業務やサービスで使えるレベルにできるようになるよう頑張りたいと思います。

ふぅ

はい、11件目の更新でした。まだまだ追いつくことができていませんが、なんとかしていきたいと思います。ネタ的には失速感が半端ないですが、続けることに意味があると自分に言い聞かせております。。(2日途切れさせたのに)

なんだか書き方的に某書籍を書いている時のような感じがしましたw

アラートメールにSlackで返事をする w/ SES + S3 + Lambda + API Gateway 〜Outgoing篇〜

10日目です。9日目が当日中にできなかったのでネタを2分割して2日分しています。すみません、すみません。

9日目にメールからSlack通知をするシステムを作りましたので、今日はSlackポストから通知されたメールに返信する部分を作成しましょう。

f:id:matetsu:20151211015200p:plain

Lambda + API Gatewayの設定

SlackのOutgoing webhooksからのリクエストを受けるためのAPI Gatewayと実際の処理を行うLambda Functionの設定を行います。

Lambdaの設定

今回はAPI Gatewayと連携してmicro serviceのEndpointとして動くことを想定しているので、選択するBlueprintはmicroservice-http-endpointになります。

f:id:matetsu:20151211005447p:plain

Lambda Functionは最初から作ってしまいましょう。RoleはIncomingのときと同じものを使います。

コードは大雑把に書くとこんな感じ。S3に格納されている元メールへの返信となるように、Subjectに「Re: 」をつけたり、元のMessage-IDだったものを「In-Reply-To」や「Resources」に設定するようにもしています。

Slackユーザとメールアドレスを紐付けることは面倒なので、no-replyからの送信にしたうえで本文に誰からの返信かを書くようにしました。


Slack -> (API Gateway -> ) Lambda -> SES (Reply Em ...

slackポストのパースは簡易的に正規表現で「@alert_bot reply 返信対象メールのS3_KEY 本文」となっていることを期待しています。また、今回はAPI Gatewayでの認証を設定しないので、outgoing webhooksで設定されているTokeがEventにセットされていない場合は処理をしないようにもしています。

API Gatewayの設定

Lambda Functionの設定の流れでAPI Gatewayの設定も出てきます。先ほど書いたとおり、今回は認証をしないでPOSTを受け付ける設定です。

f:id:matetsu:20151211011430p:plain

Lambda Functionの作成が完了すると「API Endpoint」タブが表示されているので、「Method」のPOSTのリンクをクリックしてAPI Gatewayの設定を更に進めます。

個々の設定はこちらのQiita記事が参考になるので、ご参照ください。特にMapping Templateの設定が重要ですのでお忘れなく。

qiita.com

こrでAPI Gatewayの設定も完了し、Endpoint URLが決定します。(設定時と同じくLambdaのAPI Endpointタブに表示されています)

SlackのOutgoing Webhooksの設定

裏側の設定が完了したので、次はSlack側の設定です。発言に対して処理をするにはOutgoing webhookを利用します。

設定は以下のようにします。URL欄はAPI GatewayAPI Endpoint URLを設定します。またhookのTriggerはBotへのメンションとしています。

f:id:matetsu:20151211001259p:plain

それでは、対象チャンネルで発言をしてみましょう。

f:id:matetsu:20151211014600p:plain

そしてメーラを確認すると、9日目のネタで送信したメールへの返信としてメールが来ています。

f:id:matetsu:20151211014927p:plain

うまくいきましたね。

おわりに

ニーズがあるかわかりませんが、メールへの返信を疎かにしないでSlackだけで完結できる仕組みができました。これで、ますますメール離れができますね!

返信が面倒なんで、Slackで返信が簡単にできるような仕組みがあるといいんですけどね。

そして、動かしてみて気付きましたが、Lambda Functionの返り値をincoming webhookの形式にし忘れたので、発言に対してSlackは何の反応もしません。。。これだと、ちょっと不安かもしれませんね。(今日は直さないけど)

しめ

10日目も無事書き終わりました。本当に無理くり感が半端なくなってきています。それではまた明日。

アラートメールにSlackで返事をする w/ SES + S3 + Lambda + API Gateway 〜Incoming篇〜

9日目。ネタ切れ感半端なさすぎてむりくりネタを作っています。

今日は監視システムからのアラートメールに対してSlackで返信をするというものです。最近はSlackにアラートを飛ばしたりするところも増えていますし、自分のいる部署でもメールとSlackに通知するようにしています。

こういった2つの通知先に通知を送った場合に、片方で返信をしても他方しか見ていないメンバーがいた場合に状況がわからないなんてこともあると思います。こんな状況を(第一報に対しては)解決できるであろう構成です。

f:id:matetsu:20151211015745p:plain

ニーズがあるかどうかは知りません。

Route 53の設定

メールを受信するドメインの設定をします。今回はOreginの受信用Endpointを利用するので、以下のとおりに。

f:id:matetsu:20151210004022p:plain

受信したメールを保存しておくS3バケットの設定

適当な名前でバケットを作成し、Bucket Policyに以下のようなPolicyを設定する。ドキュメントとしては、こちら

{
	"Version": "2008-10-17",
	"Statement": [
		{
			"Sid": "GiveSESPermissionToWriteEmail",
			"Effect": "Allow",
			"Principal": {
				"Service": [
					"ses.amazonaws.com"
				]
			},
			"Action": [
				"s3:PutObjectAcl",
				"s3:PutObject"
			],
			"Resource": "arn:aws:s3:::BUCKET_NAME/*",
			"Condition": {
				"StringEquals": {
					"aws:Referer": "ACCOUNT_NO"
				}
			}
		}
	]
}

SESの受信設定

受信用ドメインの認証をしますので、さきほどRoute 53でMXレコードとして設定したドメインをRecipientsとして登録して「Verify Domain」をします。

f:id:matetsu:20151210004508p:plain

「Verify Domain」をクリックするとダイアログが出てきます。今回はRoute 53を使っているので、素直に「Use Route 53」ボタンをどうぞ。

Verifyされたら次は受信時のアクションを設定していきます。今回は以下のように設定。

f:id:matetsu:20151210012226p:plain

ルールの詳細(TLS有効にするか、SPAMやウイルスチェックをするかなど)を入力。

f:id:matetsu:20151210010325p:plain

内容を確認して、「Create Rule」をします。

S3ではなく、SNSやLambdaで直接受けたほうが便利なのではとお思いでしょうが、S3でないとメールの本文を取得することができないのです。(他のブログでも書かれていますし、結構重要な点ですが、Amazonさんならそのうち取得できるようにしてくれるはず。)

イベント内容確認のためにLambda Functionの枠だけを作成しておく

Lambda Functionの作り方は、これまで何度もやってきたので省略。Blueprintのhello-world-pythonを少し変えて、以下の用意しておきます。

from __future__ import print_function

import json

print('Loading function')


def lambda_handler(event, context):
    print("Received event: " + json.dumps(event, indent=2))
   
    return "CONTINUE" 

割り当てるRoleのPolicyはこんな感じ。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::BUCKET_NAME/*"
      ]
    },
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*"
        }
  ]
}

これで、どんなEventが来たかをCloudWatch Logsから確認できます。


S3バケットに更新Eventを設定する

こんな感じで設定。先ほど作成したLambda FunctionにEventを送信するようにしています。
(名前の適当さは気にしないでくださいw)

f:id:matetsu:20151210231018p:plain

メールを受信して、eventの内容を確認

以上でメールを受信した時に発せられるEventを確認できる体制ができまたので、普段使っているメーラからSESで受信できるようにしたドメイン宛にメールを送ります(Local Partは適当で良いです)。

f:id:matetsu:20151210231449p:plain

CloudWatch LogsでEvent内容を確認

このような内容のEventが来ます。Bucket名とKey名がわかるので、これを使えばS3からメールの内容を取得することができますね。

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventTime": "2015-12-10T01:47:57.525Z",
      "requestParameters": {
        "sourceIPAddress": "IP.ADD.RE.SS"
      },
      "s3": {
        "configurationId": "notify-receiving-to-lambda",
        "object": {
          "eTag": "77ca66e4622ae4c5c113e9fd07109df0",
          "sequencer": "005668D9CD4BB230B3",
          "key": "KEY",
          "size": 1662
        },
        "bucket": {
          "arn": "arn:aws:s3:::BUCKET",
          "name": "BUCKET",
          "ownerIdentity": {
            "principalId": "PRINCIPAL_ID"
          }
        },
        "s3SchemaVersion": "1.0"
      },
      "responseElements": {
        "x-amz-id-2": "iDu7YW04ChE4HHC5HjGXC8/v1X8KzZZqNY6l7oXwulcjWQUiXY2DZfuNcHjsOfeW5Rriy8RfWTk=",
        "x-amz-request-id": "BBB6B16CB9E173EA"
      },
      "awsRegion": "ap-northeast-1",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "AWS:XXXXXXXXXXX"
      },
      "eventSource": "aws:s3"
    }
  ]
}

Lambda Functionを修正

Slackにも通知するために、こんな感じで修正します。SlackのIncoming Webhooksは各自で設定をしてください。


SES -> S3 -> Lambda -> Slack

これでテキストメールを受信するとSlackに内容が通知されます。HTMLメールやMultipartなメールはとりあえずは非対応ということで。

試してみる。

メーラからメールを送ってみると、こんな感じになります。keyと表示されてるのは、返信で使うためのS3のkeyです(イケてないですが見逃してつかあさい)。

f:id:matetsu:20151210235234p:plain

おわりに

はい、これで無事メールからSlack通知ができました。アラートメールなどの宛先に1つ追加しておけばよいので、簡単ですね。ただ、ここまでの流れであれば監視システムから直接Slackのincoming-webhooksを使ってポストしてあげれば簡単なんですけど、次回のOutgoing篇でこのシステムの真価を発揮できると思いますのでお楽しみに。

というわけで、第9日目を10日に更新するという事になってしまいましたが、無事終了です。

簡易コメントシステムからのメール通知 w/ DynamoDB streams + Lambda + SES

はい、ネタも尽きかけた今日このごろ。捻出したのは、5日目に書いたネタの拡張です。

5日目はS3(Hosting) + Cognito(AWSリソースへの時限式アクセス権の提供) + DynamoDB(Storage)と言った構成でした。これをさらに拡張して、コメント投稿があった場合には管理者にメールで通知するというものです。この拡張部分に利用するのがDynamoDBの更新イベントなどを通知するDynamoDB Streamsとメール配信サービスのSESです(どこからともなくSESではなくSendGrid*1を使ってくださいと言われそうですが、時間に限りがあるので今回はSESです)。

人間というのは面倒くさがりなので、Pushで更新通知などが無いとなかなか更新があったことに気づかないものです。そんな人でも、サーバレスで簡単に通知システムを組むことができちゃうので、安心してサイト運営ができます(サイト運営と言うとちょっとだいぶ言い過ぎですね)。

Lambda Functionの枠を用意する

今回もまずはLambda Functionの枠だけ用意して、Eventとしてどんな構造のものが来るのかを確認できるようにしましょう。BluePrintから「dynamodb-process-stream-python」を選択します。

f:id:matetsu:20151209003420p:plain

扱うデータも頻繁に更新もされませんし、更新された順に通知してくれればいよいので、Batch Sizeは「1」、Starting Positionは「Trim horizon」にします。

f:id:matetsu:20151209004059p:plain

Lambda Functionはとりあえずはサンプルのままにしておきます。

このLambda Functionで利用するIAM Roleはdynamodbを扱える必要があるのと、SESからメールを送信できる必要があるので以下のようにします。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "lambda:InvokeFunction"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetRecords",
        "dynamodb:GetShardIterator",
        "dynamodb:DescribeStream",
        "dynamodb:ListStreams",
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail"
      ],
      "Resource": "*"
    }
  ]
}

これは、「DynamoDB event stream role」というサンプルロールにSESへの権限を追加したものになります。サンプルは「role」のところから、こんな感じで選ぶ。

f:id:matetsu:20151209005220p:plain

あとは、Timeoutを10秒位にしておいて、有効にしつつ作成完了。

DynamoDBのトリガ設定

DynamoDB StreamsとLambdaを結びつけるために、トリガの設定をします。対象のテーブルでトリガを作成すると自動的にStreamが有効になります。

f:id:matetsu:20151209005712p:plain

StreamからくるLambdaのEvent内容を確認

DynamoDBに新規アイテムを追加して、どのようなEventが来るのかを確認します。適当にコメントを追記して、CloudWatchLogsを確認します。

f:id:matetsu:20151209010325p:plain

jsonの構造がわかりましたので、実際にSESで投稿内容をお知らせするLambda Functionを作成しましょう。なお、SESの設定は省略します。

Lambda Functionの修正

こんな感じ。

# coding: utf-8
import boto3
import json
import datetime

print('Loading function')


def lambda_handler(event, context):
    record = event['Records'][0]
    ses = boto3.client('ses', region_name='us-east-1')
    response = ses.send_email(
        Source = "matetsu@gmail.com",
        Destination = {
            "ToAddresses": ["matetsu@gmail.com"],
            },
        Message = {
            "Subject": {
                "Data": u"コメント投稿がありました"
                },
            "Body": {
                "Text": {
                    "Data": u"""コメント投稿がありました。
                    
                    Article: %s
                    Timestamp: %s
                    Comment: %s
                    """ % (record['dynamodb']['NewImage']['article']['S'], datetime.datetime.fromtimestamp(int(record['dynamodb']['NewImage']['timestamp']['N'])), record['dynamodb']['NewImage']['data']['S'])
                    }
                }
            }
        )

    return "Sent message id: %s" % response['MessageId']

コメントをして、メールが来るか確認

こんな投稿をしてみると。。。

f:id:matetsu:20151209015552p:plain

こんなメールが届きます。

f:id:matetsu:20151209015614p:plain

おわりに

はい、このネタもパクリやんかと言われそうですが、まあパクリと言ってしまえばパクリです。ネタがないんです。さらに、ほとんどはブログの会社さんが書いてしまっているんです。

いいわけでした。

ということで、なんとか8日目も終わりました。また明日、どんなネタが飛び出すか、お楽しみに!