株式会社フルマークス/開発エンジニア Angular JS・AWS Lambda・Docker・Swagger 直請案件8割 月 …

株式会社フルマークス/開発エンジニア◇Angular JS・AWS Lambda・Docker・Swagger◇直請案件8割◇月に1度以上の勉強会ありへの転職なら、こちらから求人情報をチェック!募集要項、企業情報、選考プロセスなどをまとめて確認できます。もちろんその場で応募も可能! – 転職は「キャリアインデックス」 続きを読む

Google Cloud Functionsが13%。CNCF調査

サーバレスコンピューティング、もっとも利用されているのはAWS Lambdaで70%、Google Cloud Functionsが13%。CNCF調査. 2017年12月12日. Kubernetesなどの開発をホストするCloud Native Computing Foundationは、同団体のコミュニティに対してコンテナオーケストレーションやサーバレスコンピューティング … 続きを読む

動画を探して自動ツイートしてくれるPython製botをAWSに載せてみた(後編)

TL;DR

  • YouTubeから動画を拾ってTweetするbotをPythonで開発し、AWS Lambdaに載せてみました
  • 全2記事です。後編のこちらでは、主にAWS Lambdaでのデプロイ・運用にフォーカスします
    • Pythonプログラムのパッケージングとアップロードに際しハマった知見を共有します
    • AWS CloudWatchと連携し、指定時間での自動ツイートを実現します
  • 前編はこちらです

AWS Lambda

AWS Lambdaとは

AWS Lambda はサーバーをプロビジョニングしたり管理しなくてもコードを実行できるコンピューティングサービスです。…コードが実行中でなければ料金はかかりません。

「一定の時刻に起動してツイート」さえしてくれればいいようなbotを動かすには、常時起動のサーバは必須ではありません。Lambdaのようなプラットフォームは今回のユースケースにうってつけと言えます。

関数をアップロードしておき、任意のイベントによって関数をトリガーするのがLambdaの基本的な使い方です。実際にやってみます。

ハマる、ハマる

が、実際には色々困りました…。順番に知見を共有します。

パッケージング

デプロイパッケージの作成 (Python)

Lambda 関数を作成するには、最初に Lambda 関数デプロイパッケージ (コードと依存関係で構成される .zip ファイル) を作成します。

ということで、プロジェクトのディレクトリに依存関係をインストールし、ZIP化する必要があります。pipのオプションでライブラリのインストール先は指定できるので、プロジェクトディレクトリに移動して…

$ pip3 install -r requirements.txt -t ./lib

ところが、エラーで失敗。

DistutilsOptionError: must supply either home or prefix/exec-prefix — not both

HomeBrewでPython導入しているとpip3 install -tが失敗する

らしいです。StackOverflowに同様の問題がありました。

ただ、回避の手段も回答されてます。ホームディレクトリに.pydistutils.cfgという名前のファイルを作って、以下の設定(というか、空のprefixを指定するハック)を書けば通るようになります。

pydistutils.cfg
[install]
prefix=

パッケージングの自動化について

pip installができるようになったのはいいとしても、増えた依存モジュールを再インストールして、もう一回.zipの中にモジュールを入れて…とか毎回手作業でやるのは辛いです。パッケージ管理ツールを導入して自動化できないかなあ、と調べたのですが色々動きが激しいようで。

ライブラリの配布について | Python Snippets

この辺りはベスプラを知りたいところではあります。追うのが大変そうだったのと、今回は大した規模でもないのでシェルスクリプトでなんとかすることにしました。

package_lambda.sh
#!/usr/bin/env bash

rm lambda.zip

cd src
pip3 install -r requirements.txt -t ./lib
zip ../lambda.zip *.py
cd lib
zip -r ../../lambda.zip *

src配下には自分で書いたPythonスクリプト群を配置しています。src/lib配下に依存モジュールをインストールし、それぞれ順番にZIPの直下に詰めてます。結論としてこれで大丈夫だったので先に進みます。

ハンドラ関数の定義

AWS Lambdaのコンソールに移ります。関数を作成、ランタイムには”Python3.6″を選びます。コードエントリで「.ZIPファイルをアップロード」を選び、作ったZipをアップしましょう。

スクリーンショット 2017-12-10 22.55.08.png

さて、次は画面右側の「ハンドラ」で処理の起点となる関数の名前を指定します。公式の説明では、

関数の filename.handler-method 値。たとえば、「main.handler」は、main.py で定義されたハンドラーメソッドを呼び出します。

すなわち[ファイル名].[関数名]とすれば良いので、前回作った感じだとmain.mainかなー、とか指定してテスト実行すると…

スクリーンショット 2017-12-10 23.08.36.png

落ちます。

ハンドラ関数の引数の数が間違っていると落ちる

"errorMessage": "main() takes 0 positional arguments but 2 were given"

Lambda 関数ハンドラー (Python)

まあドキュメントを読めという話でお恥ずかしいのですが、引数が合ってないわけですね。イベントハンドラであるところのLambda関数はeventとcontextを受けるのが基本ですから、その形に沿ったハンドラ定義が必要です。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from tube_crawler import TubeCrawler
from tweeter import Tweeter
import config

def main():
    t = TubeCrawler()
    movies = t.movies_from_query("Hybrid Rudiments")
    chosen = t.choose(movies)

    tw = Tweeter()
    tw.reply(config.REPLY_TO, chosen)
+   return chosen

+def lambda_handler(event, context):
+   result = main()
+   return { 'tweetedURL': result }

if __name__ == '__main__':
    main()

あとは、コンソールでmain.lambda_handlerをハンドラに指定すればOKです。せっかくなので実際にツイートされた動画のURLを呼び出し元に返す仕様にしてみました。

定期実行

デプロイはできたので、次は定時実行の仕組みを作ります。

スクリーンショット 2017-12-11 22.05.53.png

コンソール画面の「トリガーの追加」から「CloudWatch Events」を選びます。トリガーのルールにcron式を選べば、今回やりたいことは実現できますね。

Rate または Cron を使用したスケジュール式

  • 日または週日の値は疑問符である必要がある
  • UTCしか使用できない(ので、日本時間に合わせてずらす)

あたりが注意点でしょうか。今回は平日の定時につぶやいて欲しいので、こんな式にしてみます。

cron(15 10 ? * MON-FRI *)

運用してみて

運用して1週間ほど様子を見てみましたが、無料利用の範囲内でおおむね問題なく動いております。
今度はTedみたいな勉強系の動画やらブログ記事やら拾わせても面白いかもしれませんね。

よかったこと

一気通貫して人が使えるサービスの形まで持って行くと、否応無しに広く技術をさらうことになります。インフラよりに苦手意識があったので半ば無理やりにでも触るのはいい経験になりました。

反省

  • YouTubeAPIとか使えばもっと楽に実装できたんじゃないか疑惑
  • パッケージングとAWS周りでハマりすぎた。アップロードが絡むあたりからはTry&Errorより前にドキュメントを読もう

お読みいただきありがとうございました。

リンク

続きを読む

動画を探して自動ツイートしてくれるPython製botをAWSに載せてみた(前編)

TL;DR

  • YouTubeから動画を拾ってTweetするbotをPythonで開発し、AWS Lambdaに載せてみました
  • 全2記事です。前編のこちらでは、主にPythonでの開発周りのトピックにフォーカスします
    • TwitterAPIを使ってプログラムからツイートしてみます
    • YouTubeのページを構文解析し、文字列操作を使って動画URLを抽出してみます

動機

新しい職場にて初めてAWSを触ることになったので、これを機にと個人アカウントを取ってみました。チュートリアルだけというのももったいないので、何か自分のためのサービスを作って載せると面白そうです。

で、Twitterのbot開発にはもともと興味があったので、これも前から興味を持ちつつ触ってなかったPythonでbotを作り、lambdaを使って運用してみようと思い立ちました。AWS lambdaは2017年4月からPython3系を扱えるようになったので、心置き無く最新バージョンで書けそうだなー、というのも狙いです。

ユーザーストーリー

毎日の退勤をもう少し楽しみにするために、定時になると自分が興味ありそうなYouTube動画をbotが勝手に検索して、自分のTwitterアカウントに届けてくれるようにしたい。
スクリーンショット 2017-12-06 23.30.33.png

前提

  • 開発にはMacを使用します
  • Pythonは3.6系を使用します
  • pyenvもvirtualenvも使用しません。議論はあろうかと思いますが、個人開発なので。。
  • で、開発環境構築はこちらの記事等を参照しました
  • bot化したいTwitterアカウントはあらかじめ用意してあるものとします

TwitterAPIを使ってプログラムに呟かせる

アクセスキーの取得

bot化したいアカウントでTwitter Application Managementにログインすると、アプリケーションの作成とConsumer Key、及びAccess Tokenの取得ができます。

なお、Appの作成にはTwitterアカウントが電話番号認証済みである必要があります。認証済みでないと怒られるので、エラーメッセージ中のリンクからさらっと済ませておきましょう。

  • Consumer Key
  • Consumer Key Secret
  • Access Token
  • Access Token Secret

以上の4パラメータがあればプログラムからのツイートができます。コピーしてこんな感じのファイルを作っておきましょう。

config.py
CONSUMER_KEY        = "xxxxxxxxxxxxxxxxx"
CONSUMER_SECRET     = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ACCESS_TOKEN        = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
ACCESS_TOKEN_SECRET = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

複数の外部ユーザーからアクセスがあるようなアプリケーションの場合(=「このアプリケーションとの連携を許可しますか?」など出るやつ)はそれぞれの役割についてもう少し説明が必要ですが、今回はある程度一緒くたに考えてしまっても実装に支障ありません。

PythonでOAuth認証

ライブラリの導入と管理

Pythonのライブラリは、パッケージ管理ツールであるpipでインストールできます。仮想環境がない場合、オプション無しで勝手にglobalに入るのがうーん、という感じですがまあそれは置いておいて。

PythonでHttp通信を行うライブラリとしては、requestsがポピュラーなようです。また、今回はTwitterAPIを使うための認証が必要なので、OAuth認証を扱えるライブラリも必須です。ここはrequestsと同じところが公開しているrequests_oauthlibを使用しました。

pip3 install requests requests_oauthlib

さて、インストールはできましたが、今度は開発するプロジェクトがこれらのライブラリに依存していることを表明しておくのがマナーです。js界隈で言うところのpackage.jsonですね。

Pythonでは依存関係を記したrequirements.txtなどを作っておくケースが多いようです。

requirements.txt
requests==2.18.4
requests-oauthlib==0.8.0

ちなみに、pip3 freeze > requirements.txtでインストールされた依存関係をrequirements.txtに吐き出せます。

逆に.txtファイルを元に一括インストールする場合は、-rオプションを用いてpip3 install -r requirements.txtなどと書けます。結構便利です。

つぶやいてみる

first_tweet.py
from requests_oauthlib import OAuth1Session
import config, json

twAuth = OAuth1Session(
  config.CONSUMER_KEY,
  config.CONSUMER_SECRET,
  config.ACCESS_TOKEN,
  config.ACCESS_TOKEN_SECRET)
apiURL = "https://api.twitter.com/1.1/statuses/update.json"
params = { "status": "プログラムにツイートさせてみるテスト" }

res = twAuth.post(apiURL, params = params)
print(json.loads(res.text))

先ほど作ったconfig.pyimportして、これだけ。思ったよりだいぶ手軽です。Twitterにアクセスして実際にツイートされたことを確認しましょう!

また、せっかくなのでレスポンスをjsonライブラリでロードして吐き出してみます。

{'created_at': 'Wed Dec 06 14:00:00 +0000 2017', 'id': 9384076800000000, 'id_str': '9384076800000000', 'text': 'プログラム
にツイートさせてみるテスト', 'truncated': False, 

...(中略)...

'retweeted': False, 'lang': 'ja'}

思ったよりいろんな属性があることがわかりますね。深掘りは公式のリファレンスにて。

YouTubeから動画のURLを拾ってくる

続いて、YouTubeから動画を探してくるパートです。

Webクローリング

この分野では、「クローリング」や「スクレイピング」と言った言葉が有名です。

クローリングとスクレイピング

クローリングはウェブサイトからHTMLや任意の情報を取得する技術・行為で、 スクレイピングは取得したHTMLから任意の情報を抽出する技術・行為のことです。

たとえば、あるブログの特徴を分析したい場合を考えてみましょう。
この場合、作業の流れは

  1. そのブログサイトをクローリングする。
  2. クローリングしたHTMLからタイトルや記事の本文をスクレイピングする。
  3. スクレイピングしたタイトルや記事の本文をテキスト解析する。

というようになります。

今回は、YouTubeをクローリングし、その中から動画のURLをスクレイピングすることになりますね。

Webページのクローリングとスクレイピングを行う際は、それがどんな目的のものであれ、HTMLを構文解析することが必須となります。Pythonでは、これを強力に支援するBeautifulSoupと言うライブラリがあります。執筆時点で最新のbeautifulsoup4を導入してみます。

pip3 install beautifulsoup4

早速使ってみましょう。Qiitaのトップページから<a>タグを探し、その中に含まれるhref属性の値を取得してみます。

crawling.py
import requests
from bs4 import BeautifulSoup

URL = "https://qiita.com/"
resp = requests.get(URL)

soup = BeautifulSoup(resp.text)

# aタグの取得
a_tags = soup.find_all("a", href=True)
for a in a_tags:
    print(a["href"])

結果

/about
https://qiita.com/sessions/forgot_password
https://oauth.qiita.com/auth/github?callback_action=login_or_signup
https://oauth.qiita.com/auth/twitter?callback_action=login_or_signup

・・・(中略)

https://qiita.com/api/v2/docs
https://teams.qiita.com/
http://kobito.qiita.com

いい感じです!

HTMLパーサーについて

さて、先のコードを実際に試すと、HTMLパーサーが明示されていないために警告が出ます。これは実際の解析時に使われるパーサーが実行時の環境に依存するためです。異なる環境下で同じ振る舞いを期待するには、使用するHTMLパーサーを明示してあげる必要があります。

デフォルトではhtml.parserが使われますが、lxmlかhtml5libを導入してこちらを明示してあげるのが無難なようです。このあたりの情報は下記の記事をだいぶ参考にさせていただきました。パーサーの選択だけでなくスクレイピング全般の情報が非常によくまとまっているエントリなので、オススメです。

PythonでWebスクレイピングする時の知見をまとめておく – Stimulator

パーサの良し悪しを考えるとlxmlでチャレンジしてダメならhtml5libを試すのが良さそう。

今回はこの1文に愚直に従ってみます。事前にpip3 install lxml html5libも忘れずに。


import requests
from bs4 import BeautifulSoup

URL = "https://qiita.com/"
resp = requests.get(URL)

+try:
+  soup = BeautifulSoup(resp.text, "lxml")
+except:
+  soup = BeautifulSoup(resp.text, "html5lib")
-soup = BeautifulSoup(resp.text)

# ...以下は先ほどと同様

Crawlerクラスを作ってみる

すでにPythonでオブジェクト指向な書き方を経験している方はこの辺りを飛ばしていただいて構いません。せっかくHTMLを解析してくれるコードができたので、クラスとして書き換えてみます。

crawler.py
import requests
from bs4 import BeautifulSoup

class Crawler:
    def hrefs_from(self, URL):
        a_tags = self.soup_from(URL).find_all("a", href=True)
        return set(map(lambda a:a["href"], a_tags))

    def soup_from(self, URL):
        res_text = requests.get(URL).text
        try:
            return BeautifulSoup(res_text, "lxml")
        except:
            return BeautifulSoup(res_text, "html5lib")

個人的にはインスタンスメソッドの第1引数が常にselfでなければならないのは書く量が増えるので少しもどかしいですね。ハマりポイントにもなりかねない…。

ちなみに、ここではラムダ式を使用し、hrefs_fromメソッドの戻り値の型をsetにしてみました。これは、今回のユースケースを鑑みてリンク先URLの重複を排除した方が便利と判断したためです。出現頻度など解析したい場合はまた改めて設計を考える必要があるでしょう。

継承と、YouTubeへのアクセス

YouTubeをクローリングするにあたって、「検索文字列を与えたら検索結果のページをクローリングし、動画を探してくる」などの機能があると便利そうです。先ほどのクラスを継承して、実装してみます。

tube_crawler.py
import random
import re
from crawler import Crawler

class TubeCrawler(Crawler):

    URLBase = "https://www.youtube.com"

    def hrefs_from_query(self, key_phrase):
        """
        検索文字列を与えると検索結果ページに含まれるhref属性の値を全て返す
        """
        return super().hrefs_from(self.URLBase + 
            "/results?search_query=" + key_phrase.replace(" ", "+"))



    def movies_from_query(self, key_phrase, max_count = 10):
        """
        検索文字列を与えると検索結果ページに含まれる動画のビデオIDを返す
        """
        return self.__select_movies(self.hrefs_from_query(key_phrase), max_count)



    def __select_movies(self, hrefs, max_count):
        """
        privateメソッド。href属性の値のsetからビデオIDのみを返す
        """
        filtered = [ re.sub( "^.*/watch?v=", "", re.sub( "&(list|index)=.*$", "", href )) 
            for href in hrefs if "/watch?v=" in href ]
        return filtered[:min(max_count, len(filtered))]



    def choose(self, movie_ids, prefix = "https://youtu.be/"):
        """
        渡した文字列のリスト(ビデオIDのリストを想定)から1つを選び、prefixをつけて返す
        """
        return prefix + random.choice(movie_ids)

文法的には継承とprivateメソッドの書き方あたりが新しい話題となります。この記事の主題ではないので特段の説明は省きます。

実際に試すとわかるのですが、検索結果のページにノイズとなるリンクが多いばかりか、再生リストへのリンクなど紛らわしいものも多く、その辺を適切に弾いていくのに手こずりました。おかげでfilter関数や正規表現に少し強くなれた気がします。

正規表現についてはこちらの記事をだいぶ参考にしました。

Pythonの正規表現の基本的な使い方

繋げてみる

準備が整ったので検索->ツイートの流れを試してみます。

main.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from tube_crawler import TubeCrawler
from tweeter import Tweeter
import config

def main():
    t = TubeCrawler()
    movies = t.movies_from_query("Hybrid Rudiments")
    chosen = t.choose(movies)

    # ツイートする部分をクラス化したもの
    tw = Tweeter()
    tw.reply(config.REPLY_TO, chosen)

if __name__ == '__main__':
    main()

エントリーポイントとなる関数が必要かなー、と思ったので何気なく(そう、本当に何気なく。これで良いと思っていたんですLambdaを使うまでは…)main関数を作成。

直接./main.pyでも呼べるようにこの辺からShebangを記述し始めました。また、末尾はファイル名で直接実行した場合にmain()を呼ぶためのおまじない。Rubyにも似たやつがありますね。あとはターミナルから呼んで動作確認するだけです。

$ ./main.py

実行したところ問題なく動きそうだったので、次回はAWS Lambdaに載せていきます。それなりの尺となったのでこのページはここまでです。お読みいただきありがとうございました。

リンク

続きを読む

LambdaとCloudWatchでサーバレスなTwitterBot作ってみた

こんにちは。駆け出しインフラエンジニアの(@k4ri474)です。
この記事はVASILY Advent Calendar 2017 11日目の記事です。

Twitterでbotを作ろうと思った時、ざっくり二つ選択肢が頭に浮かびますよね?
巷に溢れているbot作成サービスに登録するか、
はたまた自作するか。

エンジニアの方は、とりあえずプログラミングしてみっか〜となることが多いような気がします。
Twitter APIに関してはもちろん公式ドキュメントがありますし、プログラムもググれば山ほど先人たちのものが見れるので、
なんちゃらキーやらなんちゃらトークンさえ取得できれば、肝心のツイートはチョチョイのジョイかと思います。

ただ、継続的に動かすbotとして考えるとベースのサーバはどうしても必要になるのでちと悩みますよね。
自分のサイトをホスティングしてメールサーバ動かしてついでにbotを回す、などといったような状況ならクラウドでサーバを立てとけばいいんですが、botだけでいい時には少々大げさです。

そこでAWS Lambda + Amazon CloudWatch Eventsを使ってサーバレスに、かつ継続的に実現します。

概要

1postするためのプログラムをpythonで書き、それをLambdaにセットします。
そしてLambdaをCloudWatch Eventsで定期的に発火させ、botの体をなすようにします。

実装(Lambda)

まず、こちらが普通のつぶやきプログラムです。

from requests_oauthlib import OAuth1Session

CK = 'CONSUMER_KEY'
CS = 'CONSUMER_SECRET'
AT = 'ACCESS_TOKEN'
AS = 'ACCESS_TOKEN_SECRET'

URL = 'https://api.twitter.com/1.1/statuses/update.json'

tweet = "Hello World"
session = OAuth1Session(CK, CS, AT, AS)

params = {"status": tweet }
session.post(URL, params = params)

Twitterに接続するにはOAuth認証が必要なのでrequests_oauthlib(https://github.com/requests/requests-oauthlib)
というPython用のOAuth認証ライブラリを利用しました。
Lambdaではライブラリとプログラムコードを一まとめにzip圧縮する必要があるので、プログラムと同じディレクトリにインストールしておきます。

% pip install requests requests_oauthlib -t ./

各種キーは苦労して取得したら、それらを各々セットしていただければあとはつぶやきをいじって実行するだけです。

さて、上のプログラムをLambda用に書き換えます。
Lambdaから関数を呼び出してもらうためには、ハンドラー関数を作成する必要があります。
パラメータとして eventcontext を取る関数で、Lambdaを呼び出したイベントデータやランタイム情報を内部で使えますが、今回はシンプルに使わない方向でいきます。

from requests_oauthlib import OAuth1Session

CK = 'CONSUMER_KEY'
CS = 'CONSUMER_SECRET'
AT = 'ACCESS_TOKEN'
AS = 'ACCESS_TOKEN_SECRET'

URL = 'https://api.twitter.com/1.1/statuses/update.json'

def my_handler(event, context):
    tweet = "Hello World"
    session = OAuth1Session(CK, CS, AT, AS)

    params = {"status": tweet }
    session.post(URL, params = params)

これで準備は完了です。
こちらのファイル群をzip化し、コマンドでアップロードしてみます。

% aws lambda create-function \
--region ap-northeast-1 \
--function-name "sample" \
--runtime "python3.6" \
--role "MY_ROLE" \
--handler "sample.my_handler" \
--zip-file "fileb://./sample.zip" \
--profile PROFILE

注意したいのはhandlerオプションです。構文は実行ファイル名.ハンドラー関数名ですので、作成したプログラム名とハンドラー関数名に合わせて適宜編集してください。

また、セットするroleにCloudWatchLogsの権限を与えておくと実行結果をログ出力できるようになるのでオススメです。
今回は定義済みのポリシー、arn:aws:iam::aws:policy/CloudWatchLogsFullAccessをアタッチしたロールを事前に作成していたので、そちらをセットしてみました。
ポリシーは以下の通りです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:*"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

以上でLambdaへのアップロードは完了です。

実装(CloudWatch Events)

さて、あとはこのLambda関数を定期実行するだけです。
まずはCloudWatch Eventsをトリガーにセットします。

スクリーンショット 2017-12-10 23.09.08.png

Lambdaのダッシュボードから関数を選択し、設定画面でトリガーを設定します。上図のようにCloudWatch Eventsをトリガーにセットしたら、下にスクロールしてトリガーの詳細を設定します。

スクリーンショット 2017-12-10 23.09.56.png

例としてはこんな感じでしょうか。
ルールタイプをスケジュール式に選択すると、おなじみのcron記述で定期実行の間隔をコントロールできます。
僕は試しにもう一方のrate式を使ってみることにしました。

このような感じで設定して保存すると無事トリガーとして機能します。
お疲れ様です。

成果物

このままのプログラムでは全く同じ内容をつぶやくばかりで面白くないので、
プログラムをちょちょっといじってimgurから猫画像のリンクを拾ってきてpostする感じのbotを作ってみました。

https://twitter.com/k4ri474/status/937562270476812290

自分の猫postで心が洗われます。
皆さんもサーバレスTwitter botでぜひ遊んでみてくださいb

続きを読む

re:Invent 2017にみるAWSとクラウドの進化する方向性

11月27日から12月1日までの5日間にわたり、ラスベガスに4万3,000人の人を集めたAWSの年次イベント”re:Invent 2017”。AWS CEOのAndy JassyやAmazon CTOのWerner Vogelsなどのキーノートでは、今年もさまざまな真サービスが発表されました。仮想環境の管理を用意にするKubernetesをマネージド型で提供するサービスやサーバーレスのデータベースサービス、マシンラーニングのモデル構築から学習、デプロイ、API化まで一気通貫でサポートするサービスなどオンラインメディアからピックアツプして紹介します。

※下記サイトからの転載。ビッグデータ・AIなどに関するトピックを毎週取り上げています。
TechCrowd: https://www.techcrowd.jp/related/

クラウドは次のフェーズへ――、「AWS re:Invent 2017」でアンディ・ジャシーCEOが示した5年間の総決算

クラウドWatchのre:Invent 2017のレポート記事です。Andy Jassyのキーノートで発表された新サービスを紹介しながら、AWSが描こうとしているあらたなクラウドの世界を展望してくれています。

まずは、各種メディアでも今年のre:Invent 2017での新サービス発表の中でも一番にとりあげられている「Amazon Elastic Container Service for Kubernetes(Amazon EKS)」。Kubernetesのマネージドサービスであり、ユーザーのVPC内でコンテナが稼働するインスタンスを起動できるほか、CloudTrailやCloudWatch、ELB(Elastic Load Balancing)といったAWSのさまざまなサービスとKubernetesのスムーズな連携が可能です。

Andy Jassy CEOはキーノートの中で、「インスタンス」「コンテナ」「サーバーレス」の3つのアーキテクチャをAWSのコンピュートリソースとして位置づけ。クラウドの基盤を支えるアーキテクチャがインスタンスという仮想サーバだけだった時代からコンテナやサーバーレスまで含むものにはっきりと拡張してきていることを感じさせます。

次に、Andy Jassy CEOがとりあげたのはデータベース。プロプライエタリなRDBからの解放、データベースの自由を実現するものとして、AWSの各種データベース機能に関する新サービスを発表。その中でももっとも注目を集めたのはAurora Multi-Master。リード重視のAuroraがリード/ライトの両方でスケールできるようになるとのこと。現時点ではシングルリージョン/マルチマスターのみのプレビュー提供だが、Andy Jassy CEOは「2018年の早い段階でマルチリージョン/マルチマスタに対応する」とのべています。

データアナリティクスをより効率的にするものとして発表されたのが、「Amazon S3 Select」と「Amazon Glacer Select」。いずれも必要なオブジェクトデータのみを標準的なSQLを使ってフィルタリング(SELECT)する機能。オブジェクト全体にアクセスする必要がなくなるため、データアクセスのパフォーマンスが最大400%と劇的に向上するとのこと。S3やGlacerが単なるオブジェクトストレージからデータレイクへと進化しつつあることを示す機能追加です。

また、マシンラーニングをより身近な存在とする新サービスもいろいろ発表されています。もっとも注目されているのが”Amazon SageMaker”。マシンラーニングのモデル構築から学習、デプロイ、API化まで一気通貫でサポートするスケーラブルなマネージドサービス。(GA、バージニア/オレゴン/オハイオ/アイルランド)

モデル作成にはデータサイエンスで標準的に使われているJupyter Notebook環境をワンクリックで設定できるほか、トレーニングもデプロイもワンクリックで利用可能。まさにオールインワンのマシンラーニングサービスとのことです。

AWSのSageMakerを使えばふつうのデベロッパーが機械学習のモデルを作れる

TechCrunshのre:Invent 2017レポート記事で、特に機械学習のモデル制作プロセスを管理するためのフレームワークを提供し、そのプロセスに含まれる複雑面倒な部分を取り去る Amazon SageMakerに焦点をあてたものです。

この新しいツールには、三つの主要部分「Notebook」「Jobs」「Models」があるとのこと。Notebookはオープンソースの標準的なツールJupyter Notebooksを使って、モデルのベースとなるデータを概観し整理する。

re:Inventのステージで、Andy Jassy CEOはSageMakerの柔軟性を強調。すぐに簡単に使えるツールとして使ってもよいし、自分のフレームワークを持ち込んでもよい。どちらの場合でも、そしてソースが何であっても、サービスはもっともポピュラーなアルゴリズム向けに調整されているとのこと。

Amazon、re:inventカンファレンスでグラフDB、Neptune発表

TechCrunshのre:Invent 2017レポート記事で、特にAWSの新しいデータベース、Amazon Neptuneに焦点をあてたものです。Amazon Neptuneは、グラフ関係の処理を目的としたサービス。サービスにソーシャルネットワーク的要素を組み込もうとしているならこのデータベースは役に立つかもしれません。

伝統的なリレーショナルDBの問題点は、もともと複雑なソーシャルグラフを扱うようにデザインされていないこと。そのためRDBでは友達関係やフォロー関係のリストを扱うのが難しく、ソーシャルグラフから共通の友達を抽出しようとすると、そのたびにきわめて複雑なクエリーを発行する必要がありました。

Neptuneは数十億に上るソーシャル関係を処理するために最適化されており、複雑なソーシャルグラフも高速に処理し、一つのクエリーを処理するのに1000分の1秒単位の時間しかかからないとのこと。

AWS re:Invent 2017、データベースもサーバーレスの時代へ

週刊BCNのre:Invent 2017レポート記事。数多くの新サービスが発表されていくなかで、
最も盛り上がったのがデータベース関連の新サービスとのことで、「Amazon Aurora Serverless」などを紹介しています。

「Amazon Aurora Serverless」は、利用した分だけ課金されるサーバーレスのリレーショナルデータベース。インスタンスの管理が不要で、自動でスケール。サーバーレスサービスの「AWS Lambda」と同様、閾値などをトリガーにSQL文を実行するなど、まずはIoT分野での活用が想定されるとのこと。また、クラウドネイティブなシステムにおいても、サーバーレスの特徴を生かした活用方法に期待が高まるとのことです。

続きを読む

AutoScalingGroupのインスタンス起動・停止をSlackに通知する

AutoScalingGroupの作成からSNSとLabmdaと連携してイベントをSlackに通知するところまで。

LaunchConfigurationの作成

AutoScalingGroupingを作るためにはLaunchTemplate, LaunchConfigurationNameまたはInstanceIdが必要なので今回はLaunchConfigurationを作成します。

$ aws autoscaling create-launch-configuration 
  --launch-configuration-name sample-auto-scale-launch-configration 
  --instance-type t2.micro 
  --image-id ami-bf4193c7
  --key-name xxxxx

AutoScalingGroupの作成

先ほど作成したLaunchConfigurationを指定してAutoScalingGroupを作成します。

$ aws autoscaling create-auto-scaling-group 
  --auto-scaling-group-name sample-auto-scale 
  --min-size 1 --max-size 5 
  --launch-configuration-name sample-auto-scale-launch-configration 
  --availability-zones us-west-2a us-west-2b

インスタンスの起動・停止を通知する

AutoScalingGroupからインスタンスのイベントを受け取る方法は LifeCycleHookでもできそうですが、今回はSNSを使った方法でやってみます。

SNSのトピックを作成する

AutoScalingGroupのイベントを通知するためのTopicを作成します。

$ aws sns create-topic --name sample-auto-scale-notification

AutoScalingGroupのイベントをSNSと紐付ける

NotificationConfigurationを追加します。
今回は以下の4つのイベントをSNSで通知するようにしました。

  • EC2_INSTANCE_LAUNCH
  • EC2_INSTANCE_LAUNCH_ERROR
  • EC2_INSTANCE_TERMINATE
  • EC2_INSTANCE_TERMINATE_ERROR
$ aws autoscaling put-notification-configuration 
  --auto-scaling-group-name sample-auto-scale 
  --topic-arn arn:aws:sns:us-west-2:999999999999:sample-auto-scale-notification 
  --notification-types "autoscaling:EC2_INSTANCE_LAUNCH" "autoscaling:EC2_INSTANCE_LAUNCH_ERROR" "autoscaling:EC2_INSTANCE_TERMINATE" "autoscaling:EC2_INSTANCE_TERMINATE_ERROR"

SNSからSlackに通知するLambdaを作成する

pythonでSlackへ通知するLambdaを作成していきます。

sns-to-slack-notificatioin-lambda.png

import urllib.request
import json

def lambda_handler(event, context):
    url = 'https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/CCCCCCCCCCCCCCCCCCCCCCCC'
    method = "POST"
    headers = {"Content-Type" : "application/json"}
    obj = {"text":json.dumps(event)}
    json_data = json.dumps(obj).encode("utf-8")
    request = urllib.request.Request(url, data=json_data, method=method, headers=headers)
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")

    return response_body

LambdaにSNSをsubscribeさせる

$ aws sns subscribe 
  --topic-arn arn:aws:sns:us-west-2:999999999999:sample-auto-scale-notification 
  --protocol lambda 
  --notification-endpoint arn:aws:lambda:us-west-2:999999999999:function:sns-to-slack-notification

SNSがLambdaを起動できるようにpermissionを与えます。

$ aws lambda add-permission 
  --function-name sns-to-slack-notification 
  --statement-id autoscaling-sns-to-lambda 
  --action "lambda:InvokeFunction" 
  --principal sns.amazonaws.com 
  --source-arn arn:aws:sns:us-west-2:999999999999:sample-auto-scale-notification

インスタンスを追加して通知を受け取る

AutoScalingGroup -> SNS -> Lambdaの設定ができたので実際にインスタンスを追加して通知を確認します。
desired-capacityを増やし、インスタンスを1つ起動します。

$ aws autoscaling set-desired-capacity 
  --auto-scaling-group-name sample-auto-scale 
  --desired-capacity 2

インスタンスが起動されSlackに通知がきました。

スクリーンショット 2017-12-10 14.50.32.png

続きを読む

AWS Lambda のアップデートが来た!今こそ AWS Step Functions を活用しよう | Developers.IO

今までデータのパース処理など、インメモリを大量に確保する必要のあるプログラムをAWS Lambda に置き換える場合、1.5GBという制約がネックになって、置き換えることが難しかったバッチプログラムもありました。 最大メモリサイズが3GBになったことで、インメモリを大量に確保するデータのパース処理も行いやすくなりました … 続きを読む

CodeBuildの実行結果をslackに通知する

はじめに

Globis Advent Calendar10日目は、弊社のエンジニアチームを支えるインフラ技術をお伝えいたします。
開発スピードをさらに促進するために、Blue Green Deployment, Infrastrucure as Code, ChatOps など新しい運用思想を積極的に取り入れて、手動でのオペレーションを極力なくしています。
今回は、弊社で実運用しているChatOpsの一例として、CodeBuildの実行結果をslackに通知する方法を、可能な範囲で具体的にお伝えいたします。

設定方法

AWS Lambdaの設定

コード

import os
import json
import urllib.request

URL = os.environ['WEBHOOK_URL']


def send_slack(obj):  ## slackに通知
    method = "POST"
    headers = {"Content-Type" : "application/json"}
    js = json.dumps(obj).encode("utf-8")
    request = urllib.request.Request(URL, data=js, method=method, headers=headers)
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")


def check_status(status):
    fail = False
    if status == 'IN_PROGRESS':
        color = '#008000'  ## green
    elif status == 'SUCCEEDED':
        color = '#00BFFF'  ## deepskyblue
    else:
        color = '#FF00FF'  ## magenta
        fail = True
    return color, fail


def parse_event(event):  ## cloudwatch event から渡された event(dict型)をパース、slackに渡すobjectを生成。
    detail = event['detail']
    status = detail['build-status']
    initiator = detail['additional-information']['initiator']
    log = detail.get('additional-information', {}).get('logs', {}).get('deep-link', 'Not Exist')
    color, fail = check_status(status)
    fields = [{'title': 'Initiator', 'value': initiator, 'short': False},
              {'title': 'Status', 'value': status, 'short': False},
              {'title': 'LogLink', 'value': log, 'short': False}]
    obj = {'attachments': [{'color': color, 'fields': fields}]}
#    if fail == True:  ## Fail時にチャンネルメンション飛ばしたい時はコメントを外す。
#        obj['attachments'][0]['text'] = '<!channel>'
    return obj


def lambda_handler(event, context):  ## lambdaから最初にcallされる関数。
    obj = parse_event(event)
    send_slack(obj)
    return

環境変数の設定

CloudWatch Event の設定

slackでの通知結果

  • ワンクリックでビルドログを眺めることができます。

運用事例のご紹介

  • AWS Athenaのテーブルにクエリを実行し、中間テーブルを生成するETL処理のバッチ
  • Ruby on Rails アプリケーションデプロイ時の db:migrate 処理
  • 最近はCodeBuildをVPC内で実行できるようになったので、利用できる幅が広がっています!

おわりに

いかがでしたでしょうか。python初心者(僕)でもChatOpsに貢献できます。
グロービスではSREエンジニアを募集しています!
インフラの運用担当だけど手作業を自動化したいと考えている方、開発者だけどインフラも含めてコードで管理したい方、一緒に理想のインフラを作ってみませんか?

続きを読む

AWS Lambdaでストリームベースのイベントソースのリトライ回数を有限にしてDLQを使う

はじめに

AWS Lambdaのリトライの挙動は、「ストリームベースでないイベントソース」と「ストリームベースのイベントソース」で大きく異なります1

参考: エラー時の再試行

  • ストリームベースでないイベントソース

    • 非同期呼び出し

      • 2回のリトライを含め、合計3回実行される
    • 同期呼び出し
      • 呼び出し側にエラーが返る(リトライする場合は、呼び出し側が再度呼び出す)
  • ストリームベースのイベントソース
    • データの有効期限が切れるまで、無限にリトライ

各トリガがどちらのイベントソースに該当するかについては、基本的には、「ストリームベースのイベントソース」はAmazon KinesisストリームとDynamoDB ストリーム、「ストリーベースでないイベントソース」はそれ以外、だと覚えておけばよいかと思います。

ストリームベースのイベントソースのリトライを有限にしたい

さて、本題です。

KinesisストリームやDynamoDBストリームのLambdaハンドラで、エラーがあった場合、成功するか有効期限が切れるまで延々とリトライが繰り返されるわけですが、場合によっては、有限のリトライを行った後、デッドレターキューに詰めた上で、次のデータを処理してほしいケースがあるかもしれません。

そのための方法を考えてみました。

やり方は単純で、単体のLambdaの中でDynamoDBイベントのハンドリングと実処理を行なうのではなく、Lambdaを分割し、実処理を行うLambdaを非同期でキック(invoke)して実行させる、というものです。

なんのこっちゃ、ですが、図にすると簡単です。

Dynamo-Lambda.png

要するに、Lambdaを多段構成にしてしまうわけですね。2つのLambdaの役割は次のようになります。

  • dynamo-handler

    • DynamoDBストリームからイベントを受け取り、実処理を行なうLambda(dynamo-processor)を非同期で呼び出す
  • dynamo-processor
    • 実際のDynamoDBストリームイベントを処理する
    • 「ストリームベースでないイベントソース(非同期呼び出し)」としての扱いになるので、2回のリトライやデッドレターキューが利用可能になる

具体的なコードは次のようになります。

dynamo-handler

dynamo-handler
var aws = require('aws-sdk');

exports.handler = (event, context, callback) => {    
    var lambda = new aws.Lambda({apiVersion: '2014-11-11'});
    var params = {
        FunctionName: "dynamo-processor",
        InvokeArgs: JSON.stringify({ Records: event.Records }, null, ' ')
    };

    lambda.invokeAsync(params, (err, data) => {
        if(err) callback(null, 'error ' + JSON.stringify(err));
        else callback(null, 'success ' + JSON.stringify(data));
    });
};

dynamo-processor がイベントを処理できるようにするために、 event.RecordsInvokeArgs で渡してあげることです。

dynamo-processor

dynamo-processor
exports.handler = (event, context, callback) => {
    var records = event.Records;

    // 実際の処理を書く(略)

    callback(null, 'success');
};

実際の処理は適当に…。

DynamoDBやデッドレターキューの設定は省略します。

わざと常に失敗する dynamo-processor を作って試してみましたが、3回実行された後、Dead Letter Queueに入っていることが確認できました。

リトライの待機時間

リトライがどれくらいの間隔で行われるのかについてですが、AWSのドキュメントには、

再試行間には遅延があります

としか書かれていません (http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/retries-on-errors.html)

実際にどれくらいになったのか、参考値として今回の実験結果を紹介します。

  • 1件目

    • 1回目 13:49:11
    • 2回目 13:50:17
    • 3回目 14:00:17
  • 2件目
    • 1回目 13:57:19
    • 2回目 13:58:17
    • 3回目 14:00:14

となりました。

非同期呼び出しにしたため、3回目の実行時刻で、1件目と2件目で追い越しが発生していますね。
試行回数が少なすぎてなんとも言えないですが、最初のリトライは大体1分後に行われる、のかもしれません。

おわりに

KinesisストリームやDynamoDBストリームをトリガにしたLambdaでのリトライ・DLQについてでした。

注意点として、このような変更を加えることで、実行順序は保証されなくなります。
したがって、アプリケーションが実行順序が保証されなくても問題ないような設計、実装になっているか、そもそも要件的に問題がないか、きちんと検討が必要です。
ご利用は計画的に…。


  1. リトライの挙動だけでなく、同時実行数などでも違いがあります。 

続きを読む